diff options
1042 files changed, 31213 insertions, 10640 deletions
diff --git a/Android.bp b/Android.bp index 1a73e9d323ff..ff6210626ecc 100644 --- a/Android.bp +++ b/Android.bp @@ -1115,6 +1115,7 @@ filegroup { "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 000000000000..fbc611a39d7d --- /dev/null +++ b/MULTIUSER_OWNERS @@ -0,0 +1,4 @@ +# OWNERS of Multiuser related files +bookatz@google.com +omakoto@google.com +yamasani@google.com @@ -9,6 +9,7 @@ hackbod@google.com jjaggi@google.com jsharkey@android.com jsharkey@google.com +lorenzo@google.com michaelwr@google.com nandana@google.com narayan@google.com diff --git a/apct-tests/perftests/core/src/android/mtp_perf/AppFusePerfTest.java b/apct-tests/perftests/core/src/android/mtp_perf/AppFusePerfTest.java new file mode 100644 index 000000000000..fcbfc7212351 --- /dev/null +++ b/apct-tests/perftests/core/src/android/mtp_perf/AppFusePerfTest.java @@ -0,0 +1,122 @@ +/* + * 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.mtp_perf; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.os.ProxyFileDescriptorCallback; +import android.os.storage.StorageManager; +import android.system.ErrnoException; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class AppFusePerfTest { + static final int SIZE = 10 * 1024 * 1024; // 10MB + + @Test + public void testReadWriteFile() throws IOException { + final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + final StorageManager storageManager = context.getSystemService(StorageManager.class); + + final byte[] bytes = new byte[SIZE]; + final int samples = 100; + final double[] readTime = new double[samples]; + final double[] writeTime = new double[samples]; + + for (int i = 0; i < samples; i++) { + final ParcelFileDescriptor fd = storageManager.openProxyFileDescriptor( + ParcelFileDescriptor.MODE_READ_ONLY, new TestCallback()); + try (final ParcelFileDescriptor.AutoCloseInputStream stream = + new ParcelFileDescriptor.AutoCloseInputStream(fd)) { + final long startTime = System.nanoTime(); + stream.read(bytes); + readTime[i] = (System.nanoTime() - startTime) / 1000.0 / 1000.0; + } + } + + for (int i = 0; i < samples; i++) { + final ParcelFileDescriptor fd = storageManager.openProxyFileDescriptor( + ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE, + new TestCallback()); + try (final ParcelFileDescriptor.AutoCloseOutputStream stream = + new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { + final long startTime = System.nanoTime(); + stream.write(bytes); + writeTime[i] = (System.nanoTime() - startTime) / 1000.0 / 1000.0; + } + } + + double readAverage = 0; + double writeAverage = 0; + double readSquaredAverage = 0; + double writeSquaredAverage = 0; + for (int i = 0; i < samples; i++) { + readAverage += readTime[i]; + writeAverage += writeTime[i]; + readSquaredAverage += readTime[i] * readTime[i]; + writeSquaredAverage += writeTime[i] * writeTime[i]; + } + + readAverage /= samples; + writeAverage /= samples; + readSquaredAverage /= samples; + writeSquaredAverage /= samples; + + final Bundle results = new Bundle(); + results.putDouble("readAverage", readAverage); + results.putDouble("readStandardDeviation", + Math.sqrt(readSquaredAverage - readAverage * readAverage)); + results.putDouble("writeAverage", writeAverage); + results.putDouble("writeStandardDeviation", + Math.sqrt(writeSquaredAverage - writeAverage * writeAverage)); + InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, results); + } + + private static class TestCallback extends ProxyFileDescriptorCallback { + @Override + public long onGetSize() throws ErrnoException { + return SIZE; + } + + @Override + public int onRead(long offset, int size, byte[] data) throws ErrnoException { + return size; + } + + @Override + public int onWrite(long offset, int size, byte[] data) throws ErrnoException { + return size; + } + + @Override + public void onFsync() throws ErrnoException {} + + @Override + public void onRelease() {} + } +} diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt index ae9e7ff8de2f..ae32fba443f6 100644 --- a/apex/appsearch/framework/api/current.txt +++ b/apex/appsearch/framework/api/current.txt @@ -100,6 +100,7 @@ package android.app.appsearch { 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 @@ package android.app.appsearch { 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 @@ package android.app.appsearch { 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 @@ package android.app.appsearch { 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 85207f7ed9d7..11e7fab2b7d9 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.IntRange; 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 @@ public class GenericDocument { /** 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 @@ public class GenericDocument { } /** + * 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 8b20c0903ce1..43be442bd4dc 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 com.android.internal.util.Preconditions; 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 1c360a65a041..b9503eed153c 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 @@ package android.app.appsearch; 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 5ffa7c94087c..eb0b7324a117 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 @@ public final class SearchResult { /** @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 @@ public final class SearchResult { @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 @@ public final class SearchResult { @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 @@ public final class SearchResult { } /** + * 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 400b6303d12e..c3f0d8ab53e3 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 @@ package android.app.appsearch; 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 ad3ee0587f61..e9c4cb4e9e34 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 @@ public final class SetSchemaRequest { /** * 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 @@ public final class SetSchemaRequest { * certificate. * * <p>This method is inefficient to call repeatedly. - * @hide */ @NonNull public Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackages() { @@ -141,7 +139,6 @@ public final class SetSchemaRequest { * * @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 @@ public final class SetSchemaRequest { * @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 704f180bffc4..b1a33a478a47 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 @@ import android.app.appsearch.AppSearchResult; * * <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 47a81eb37c7d..a2126b19d31e 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.SearchResult 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 @@ public final class AppSearchImpl { 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 @@ public final class AppSearchImpl { * 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 @@ public final class AppSearchImpl { 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 @@ public final class AppSearchImpl { 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 @@ public final class AppSearchImpl { 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 @@ public final class AppSearchImpl { } /** 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 5474cd04287c..a2386eccc256 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 @@ public final class GenericDocumentToProtoConverter { 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 4d107a970abd..ccd567d1c945 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.SearchResult; 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 @@ import java.util.ArrayList; 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 2b1ec08e1d8e..73f64dc500ae 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 9e22bf6ddbbf..6859747286a8 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 @@ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim { 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 8383df4dd0cd..e439c5ab3947 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 @@ import java.util.Set; * 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 @@ public interface AppSearchSessionShim { * 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 @@ public interface AppSearchSessionShim { * <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 @@ public interface AppSearchSessionShim { * 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 @@ public interface AppSearchSessionShim { * 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 @@ public interface AppSearchSessionShim { * 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 @@ public interface AppSearchSessionShim { * 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 @@ public interface AppSearchSessionShim { /** * 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 33dc3795325b..2d09247dfbc9 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 @@ public interface GlobalSearchSessionShim { * 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 f387a1740386..328c65ca2727 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.io.Closeable; 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 @@ public interface SearchResultsShim extends Closeable { /** * 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/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java index 4dc9cf850893..cc3e9c33fe42 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 @@ public class AppStateTrackerImpl implements AppStateTracker { 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 @@ public class AppStateTrackerImpl implements AppStateTracker { final boolean isActive = sender.isUidActive(uid); updateJobsForUid(uid, isActive); + updateAlarmsForUid(uid); if (isActive) { unblockAlarmsForUid(uid); @@ -313,7 +311,7 @@ public class AppStateTrackerImpl implements AppStateTracker { */ private void onPowerSaveUnexempted(AppStateTrackerImpl sender) { updateAllJobs(); - unblockAllUnrestrictedAlarms(); + updateAllAlarms(); } /** @@ -322,6 +320,8 @@ public class AppStateTrackerImpl implements AppStateTracker { */ private void onPowerSaveExemptionListChanged(AppStateTrackerImpl sender) { updateAllJobs(); + updateAllAlarms(); + unblockAllUnrestrictedAlarms(); } /** @@ -344,7 +344,7 @@ public class AppStateTrackerImpl implements AppStateTracker { 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 @@ public class AppStateTrackerImpl implements AppStateTracker { */ private void onForceAllAppsStandbyChanged(AppStateTrackerImpl sender) { updateAllJobs(); - - if (!sender.isForceAllAppsStandbyEnabled()) { - unblockAllUnrestrictedAlarms(); - } + updateAllAlarms(); } /** @@ -387,6 +384,19 @@ public class AppStateTrackerImpl implements AppStateTracker { } /** + * 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 @@ public class AppStateTrackerImpl implements AppStateTracker { // Feature flag for forced app standby changed. final boolean unblockAlarms; synchronized (mLock) { - unblockAlarms = !mForcedAppStandbyEnabled && !mForceAllAppsStandby; + unblockAlarms = !mForcedAppStandbyEnabled; } for (Listener l : cloneListeners()) { l.updateAllJobs(); @@ -1109,53 +1119,64 @@ public class AppStateTrackerImpl implements AppStateTracker { } /** - * @return whether alarms should be restricted for a UID package-name. - */ - 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. + * @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 areJobsRestricted(int uid, @NonNull String packageName, - boolean hasForegroundExemption) { - return isRestricted(uid, packageName, /*useTempExemptionListToo=*/ true, - hasForegroundExemption); + public boolean areAlarmsRestricted(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; + } + return (mForcedAppStandbyEnabled && isRunAnyRestrictedLocked(uid, packageName)); + } } /** - * @return whether foreground services should be suppressed in the background - * due to forced app standby for the given app + * @return whether alarms should be restricted when due to battery saver. */ - public boolean areForegroundServicesRestricted(int uid, @NonNull String packageName) { + public boolean areAlarmsRestrictedByBatterySaver(int uid, @NonNull String packageName) { + if (isUidActive(uid)) { + return false; + } synchronized (mLock) { - return isRunAnyRestrictedLocked(uid, packageName); + 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 force-app-standby is effective for a UID package-name. + * @return whether jobs should be restricted for a UID package-name. This could be due to + * battery saver or user-forced app standby */ - private boolean isRestricted(int uid, @NonNull String packageName, - boolean useTempExemptionListToo, boolean exemptOnBatterySaver) { + 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)) { - return false; - } - if (useTempExemptionListToo && ArrayUtils.contains(mTempExemptAppIds, appId)) { + 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 a8c0f0ec3e00..657c368d0aee 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 @@ import java.util.Date; */ 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 @@ class Alarm { */ 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 @@ class Alarm { 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 7842d487dfb2..aa46cfdc5c8a 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.content.pm.PackageManager.MATCH_SYSTEM_ONLY; 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { * 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { } /** + * 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 @@ public class AlarmManagerService extends SystemService { if (a.alarmClock != null) { mNextAlarmClockMayChange = true; } + adjustDeliveryTimeBasedOnBatterySaver(a); adjustDeliveryTimeBasedOnBucketLocked(a); mAlarmStore.add(a); rescheduleKernelAlarmsLocked(); @@ -2230,14 +2269,6 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { } } + 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { } 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 @@ public class AlarmManagerService extends SystemService { @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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index df0a0ee38674..35dadf06f4b8 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1978,9 +1978,12 @@ public class JobSchedulerService extends com.android.server.SystemService JobStatus runNow = (JobStatus) message.obj; // runNow can be null, which is a controller's way of indicating that its // state is such that all ready jobs should be run immediately. - if (runNow != null && isReadyToBeExecutedLocked(runNow)) { - mJobPackageTracker.notePending(runNow); - addOrderedItem(mPendingJobs, runNow, sPendingJobComparator); + if (runNow != null) { + if (!isCurrentlyActiveLocked(runNow) + && isReadyToBeExecutedLocked(runNow)) { + mJobPackageTracker.notePending(runNow); + addOrderedItem(mPendingJobs, runNow, sPendingJobComparator); + } } else { queueReadyJobsForExecutionLocked(); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index 6ddafadaa871..d5130dc97bbc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -217,20 +217,6 @@ public final class ConnectivityController extends RestrictingController implemen return jobs != null && jobs.size() > 0; } - @VisibleForTesting - @GuardedBy("mLock") - boolean wouldBeReadyWithConnectivityLocked(JobStatus jobStatus) { - final boolean networkAvailable = isNetworkAvailable(jobStatus); - if (DEBUG) { - Slog.v(TAG, "wouldBeReadyWithConnectivityLocked: " + jobStatus.toShortString() - + " networkAvailable=" + networkAvailable); - } - // If the network isn't available, then requesting an exception won't help. - - return networkAvailable && wouldBeReadyWithConstraintLocked(jobStatus, - JobStatus.CONSTRAINT_CONNECTIVITY); - } - /** * Tell NetworkPolicyManager not to block a UID's network connection if that's the only * thing stopping a job from running. @@ -243,7 +229,8 @@ public final class ConnectivityController extends RestrictingController implemen } // Always check the full job readiness stat in case the component has been disabled. - if (wouldBeReadyWithConnectivityLocked(jobStatus)) { + if (wouldBeReadyWithConstraintLocked(jobStatus, JobStatus.CONSTRAINT_CONNECTIVITY) + && isNetworkAvailable(jobStatus)) { if (DEBUG) { Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would be ready."); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 2d55aa5d5495..a02f8de5e292 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -43,6 +43,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManagerInternal; import android.os.BatteryManager; import android.os.BatteryManagerInternal; import android.os.Handler; @@ -64,6 +65,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.server.JobSchedulerBackgroundThread; import com.android.server.LocalServices; import com.android.server.job.ConstantsProto; @@ -507,6 +509,8 @@ public final class QuotaController extends StateController { QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS }; + private long mEjLimitSpecialAdditionMs = QcConstants.DEFAULT_EJ_LIMIT_SPECIAL_ADDITION_MS; + /** * The period of time used to calculate expedited job sessions. Apps can only have expedited job * sessions totalling {@link #mEJLimitsMs}[bucket within this period of time (without factoring @@ -517,22 +521,26 @@ public final class QuotaController extends StateController { /** * Length of time used to split an app's top time into chunks. */ - public long mEJTopAppTimeChunkSizeMs = QcConstants.DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; + private long mEJTopAppTimeChunkSizeMs = QcConstants.DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; /** * How much EJ quota to give back to an app based on the number of top app time chunks it had. */ - public long mEJRewardTopAppMs = QcConstants.DEFAULT_EJ_REWARD_TOP_APP_MS; + private long mEJRewardTopAppMs = QcConstants.DEFAULT_EJ_REWARD_TOP_APP_MS; /** * How much EJ quota to give back to an app based on each non-top user interaction. */ - public long mEJRewardInteractionMs = QcConstants.DEFAULT_EJ_REWARD_INTERACTION_MS; + private long mEJRewardInteractionMs = QcConstants.DEFAULT_EJ_REWARD_INTERACTION_MS; /** * How much EJ quota to give back to an app based on each notification seen event. */ - public long mEJRewardNotificationSeenMs = QcConstants.DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS; + private long mEJRewardNotificationSeenMs = QcConstants.DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS; + + /** The package verifier app. */ + @Nullable + private String mPackageVerifier; /** An app has reached its quota. The message should contain a {@link Package} object. */ @VisibleForTesting @@ -588,6 +596,16 @@ public final class QuotaController extends StateController { } @Override + public void onSystemServicesReady() { + String[] pkgNames = LocalServices.getService(PackageManagerInternal.class) + .getKnownPackageNames( + PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM); + synchronized (mLock) { + mPackageVerifier = ArrayUtils.firstOrNull(pkgNames); + } + } + + @Override public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { final int userId = jobStatus.getSourceUserId(); final String pkgName = jobStatus.getSourcePackageName(); @@ -875,7 +893,7 @@ public final class QuotaController extends StateController { if (quota.getStandbyBucketLocked() == NEVER_INDEX) { return 0; } - final long limitMs = mEJLimitsMs[quota.getStandbyBucketLocked()]; + final long limitMs = getEJLimitMsLocked(packageName, quota.getStandbyBucketLocked()); long remainingMs = limitMs - quota.getTallyLocked(); // Stale sessions may still be factored into tally. Make sure they're removed. @@ -912,6 +930,14 @@ public final class QuotaController extends StateController { return remainingMs - timer.getCurrentDuration(sElapsedRealtimeClock.millis()); } + private long getEJLimitMsLocked(@NonNull final String packageName, final int standbyBucket) { + final long baseLimitMs = mEJLimitsMs[standbyBucket]; + if (packageName.equals(mPackageVerifier)) { + return baseLimitMs + mEjLimitSpecialAdditionMs; + } + return baseLimitMs; + } + /** * Returns the amount of time, in milliseconds, until the package would have reached its * duration quota, assuming it has a job counting towards its quota the entire time. This takes @@ -1014,7 +1040,7 @@ public final class QuotaController extends StateController { final long nowElapsed = sElapsedRealtimeClock.millis(); ShrinkableDebits quota = getEJQuotaLocked(userId, packageName); - final long limitMs = mEJLimitsMs[quota.getStandbyBucketLocked()]; + final long limitMs = getEJLimitMsLocked(packageName, quota.getStandbyBucketLocked()); final long startWindowElapsed = Math.max(0, nowElapsed - mEJLimitWindowSizeMs); long remainingDeadSpaceMs = remainingExecutionTimeMs; // Total time looked at where a session wouldn't be phasing out. @@ -1606,7 +1632,7 @@ public final class QuotaController extends StateController { inRegularQuotaTimeElapsed = inQuotaTimeElapsed; } if (remainingEJQuota <= 0) { - final long limitMs = mEJLimitsMs[standbyBucket] - mQuotaBufferMs; + final long limitMs = getEJLimitMsLocked(packageName, standbyBucket) - mQuotaBufferMs; long sumMs = 0; final Timer ejTimer = mEJPkgTimers.get(userId, packageName); if (ejTimer != null && ejTimer.isActive()) { @@ -2741,6 +2767,9 @@ public final class QuotaController extends StateController { static final String KEY_EJ_LIMIT_RESTRICTED_MS = QC_CONSTANT_PREFIX + "ej_limit_restricted_ms"; @VisibleForTesting + static final String KEY_EJ_LIMIT_SPECIAL_ADDITION_MS = + QC_CONSTANT_PREFIX + "ej_limit_special_addition_ms"; + @VisibleForTesting static final String KEY_EJ_WINDOW_SIZE_MS = QC_CONSTANT_PREFIX + "ej_window_size_ms"; @VisibleForTesting @@ -2801,6 +2830,7 @@ public final class QuotaController extends StateController { private static final long DEFAULT_EJ_LIMIT_FREQUENT_MS = 10 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_LIMIT_RARE_MS = DEFAULT_EJ_LIMIT_FREQUENT_MS; private static final long DEFAULT_EJ_LIMIT_RESTRICTED_MS = 5 * MINUTE_IN_MILLIS; + private static final long DEFAULT_EJ_LIMIT_SPECIAL_ADDITION_MS = 30 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_WINDOW_SIZE_MS = 24 * HOUR_IN_MILLIS; private static final long DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS = 30 * SECOND_IN_MILLIS; private static final long DEFAULT_EJ_REWARD_TOP_APP_MS = 10 * SECOND_IN_MILLIS; @@ -3001,6 +3031,11 @@ public final class QuotaController extends StateController { public long EJ_LIMIT_RESTRICTED_MS = DEFAULT_EJ_LIMIT_RESTRICTED_MS; /** + * How much additional EJ quota special, critical apps should get. + */ + public long EJ_LIMIT_SPECIAL_ADDITION_MS = DEFAULT_EJ_LIMIT_SPECIAL_ADDITION_MS; + + /** * The period of time used to calculate expedited job sessions. Apps can only have expedited * job sessions totalling EJ_LIMIT_<bucket>_MS within this period of time (without factoring * in any rewards or free EJs). @@ -3053,6 +3088,7 @@ public final class QuotaController extends StateController { case KEY_EJ_LIMIT_FREQUENT_MS: case KEY_EJ_LIMIT_RARE_MS: case KEY_EJ_LIMIT_RESTRICTED_MS: + case KEY_EJ_LIMIT_SPECIAL_ADDITION_MS: case KEY_EJ_WINDOW_SIZE_MS: updateEJLimitConstantsLocked(); break; @@ -3376,7 +3412,8 @@ public final class QuotaController extends StateController { DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_EJ_LIMIT_ACTIVE_MS, KEY_EJ_LIMIT_WORKING_MS, KEY_EJ_LIMIT_FREQUENT_MS, KEY_EJ_LIMIT_RARE_MS, - KEY_EJ_LIMIT_RESTRICTED_MS, KEY_EJ_WINDOW_SIZE_MS); + KEY_EJ_LIMIT_RESTRICTED_MS, KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, + KEY_EJ_WINDOW_SIZE_MS); EJ_LIMIT_ACTIVE_MS = properties.getLong( KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS); EJ_LIMIT_WORKING_MS = properties.getLong( @@ -3387,6 +3424,8 @@ public final class QuotaController extends StateController { KEY_EJ_LIMIT_RARE_MS, DEFAULT_EJ_LIMIT_RARE_MS); EJ_LIMIT_RESTRICTED_MS = properties.getLong( KEY_EJ_LIMIT_RESTRICTED_MS, DEFAULT_EJ_LIMIT_RESTRICTED_MS); + EJ_LIMIT_SPECIAL_ADDITION_MS = properties.getLong( + KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, DEFAULT_EJ_LIMIT_SPECIAL_ADDITION_MS); EJ_WINDOW_SIZE_MS = properties.getLong( KEY_EJ_WINDOW_SIZE_MS, DEFAULT_EJ_WINDOW_SIZE_MS); @@ -3432,6 +3471,13 @@ public final class QuotaController extends StateController { mEJLimitsMs[RESTRICTED_INDEX] = newRestrictedLimitMs; mShouldReevaluateConstraints = true; } + // The addition must be in the range [0 minutes, window size - active limit]. + long newSpecialAdditionMs = Math.max(0, + Math.min(newWindowSizeMs - newActiveLimitMs, EJ_LIMIT_SPECIAL_ADDITION_MS)); + if (mEjLimitSpecialAdditionMs != newSpecialAdditionMs) { + mEjLimitSpecialAdditionMs = newSpecialAdditionMs; + mShouldReevaluateConstraints = true; + } } private void dump(IndentingPrintWriter pw) { @@ -3470,6 +3516,7 @@ public final class QuotaController extends StateController { pw.print(KEY_EJ_LIMIT_FREQUENT_MS, EJ_LIMIT_FREQUENT_MS).println(); pw.print(KEY_EJ_LIMIT_RARE_MS, EJ_LIMIT_RARE_MS).println(); pw.print(KEY_EJ_LIMIT_RESTRICTED_MS, EJ_LIMIT_RESTRICTED_MS).println(); + pw.print(KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, EJ_LIMIT_SPECIAL_ADDITION_MS).println(); pw.print(KEY_EJ_WINDOW_SIZE_MS, EJ_WINDOW_SIZE_MS).println(); pw.print(KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, EJ_TOP_APP_TIME_CHUNK_SIZE_MS).println(); pw.print(KEY_EJ_REWARD_TOP_APP_MS, EJ_REWARD_TOP_APP_MS).println(); @@ -3593,6 +3640,11 @@ public final class QuotaController extends StateController { } @VisibleForTesting + long getEjLimitSpecialAdditionMs() { + return mEjLimitSpecialAdditionMs; + } + + @VisibleForTesting @NonNull long getEJLimitWindowSizeMs() { return mEJLimitWindowSizeMs; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java index 2b5aab83463b..ede14ec06c71 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java @@ -157,31 +157,27 @@ public final class TimeController extends StateController { && !job.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE) && job.getLatestRunTimeElapsed() <= mNextJobExpiredElapsedMillis) { if (evaluateDeadlineConstraint(job, nowElapsedMillis)) { - checkExpiredDeadlinesAndResetAlarm(); - checkExpiredDelaysAndResetAlarm(); - } else { - final boolean isAlarmForJob = - job.getLatestRunTimeElapsed() == mNextJobExpiredElapsedMillis; - final boolean wouldBeReady = wouldBeReadyWithConstraintLocked( - job, JobStatus.CONSTRAINT_DEADLINE); - if ((isAlarmForJob && !wouldBeReady) || (!isAlarmForJob && wouldBeReady)) { - checkExpiredDeadlinesAndResetAlarm(); + if (job.isReady()) { + // If the job still isn't ready, there's no point trying to rush the + // Scheduler. + mStateChangedListener.onRunJobNow(job); } + } else if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) { + // This job's deadline is earlier than the current set alarm. Update the alarm. + setDeadlineExpiredAlarmLocked(job.getLatestRunTimeElapsed(), + deriveWorkSource(job.getSourceUid(), job.getSourcePackageName())); } } if (job.hasTimingDelayConstraint() && !job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY) && job.getEarliestRunTime() <= mNextDelayExpiredElapsedMillis) { - if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) { - checkExpiredDelaysAndResetAlarm(); - } else { - final boolean isAlarmForJob = - job.getEarliestRunTime() == mNextDelayExpiredElapsedMillis; - final boolean wouldBeReady = wouldBeReadyWithConstraintLocked( - job, JobStatus.CONSTRAINT_TIMING_DELAY); - if ((isAlarmForJob && !wouldBeReady) || (!isAlarmForJob && wouldBeReady)) { - checkExpiredDelaysAndResetAlarm(); - } + // Since this is just the delay, we don't need to rush the Scheduler to run the job + // immediately if the constraint is satisfied here. + if (!evaluateTimingDelayConstraint(job, nowElapsedMillis) + && wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) { + // This job's delay is earlier than the current set alarm. Update the alarm. + setDelayExpiredAlarmLocked(job.getEarliestRunTime(), + deriveWorkSource(job.getSourceUid(), job.getSourcePackageName())); } } } diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java index d449289f156f..3d706e40bc0b 100644 --- a/apex/media/framework/java/android/media/MediaTranscodeManager.java +++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java @@ -109,6 +109,9 @@ public final class MediaTranscodeManager { /** Interval between trying to reconnect to the service. */ private static final int INTERVAL_CONNECT_SERVICE_RETRY_MS = 40; + /** Default bpp(bits-per-pixel) to use for calculating default bitrate. */ + private static final float BPP = 0.25f; + /** * Default transcoding type. * @hide @@ -1002,15 +1005,93 @@ public final class MediaTranscodeManager { if (!shouldTranscode()) { return null; } - // TODO(hkuang): Only modified the video codec type, and use fixed bitrate for now. - // May switch to transcoding profile when it's available. + MediaFormat videoTrackFormat = new MediaFormat(mSrcVideoFormatHint); videoTrackFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); - videoTrackFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); + + int width = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_WIDTH); + int height = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_HEIGHT); + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException( + "Source Width and height must be larger than 0"); + } + + // TODO(hkuang): Remove the hardcoded frameRate after b/176940364 is fixed. + float frameRate = (float) 30.0; + /*mSrcVideoFormatHint.getFloat(MediaFormat.KEY_FRAME_RATE, frameRate); + if (frameRate <= 0) { + throw new IllegalArgumentException( + "frameRate must be larger than 0"); + }*/ + + int bitrate = getAVCBitrate(width, height, frameRate); + videoTrackFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); return videoTrackFormat; } /** + * Generate a default bitrate with the fixed bpp(bits-per-pixel) 0.25. + * This maps to: + * 1080P@30fps -> 16Mbps + * 1080P@60fps-> 32Mbps + * 4K@30fps -> 62Mbps + */ + private static int getDefaultBitrate(int width, int height, float frameRate) { + return (int) (width * height * frameRate * BPP); + } + + /** + * Query the bitrate from CamcorderProfile. If there are two profiles that match the + * width/height/framerate, we will use the higher one to get better quality. + * Return default bitrate if could not find any match profile. + */ + private static int getAVCBitrate(int width, int height, float frameRate) { + int bitrate = -1; + int[] cameraIds = {0, 1}; + + // Profiles ordered in decreasing order of preference. + int[] preferQualities = { + CamcorderProfile.QUALITY_2160P, + CamcorderProfile.QUALITY_1080P, + CamcorderProfile.QUALITY_720P, + CamcorderProfile.QUALITY_480P, + CamcorderProfile.QUALITY_LOW, + }; + + for (int cameraId : cameraIds) { + for (int quality : preferQualities) { + // Check if camera id has profile for the quality level. + if (!CamcorderProfile.hasProfile(cameraId, quality)) { + continue; + } + CamcorderProfile profile = CamcorderProfile.get(cameraId, quality); + // Check the width/height/framerate/codec, also consider portrait case. + if (((width == profile.videoFrameWidth + && height == profile.videoFrameHeight) + || (height == profile.videoFrameWidth + && width == profile.videoFrameHeight)) + && (int) frameRate == profile.videoFrameRate + && profile.videoCodec == MediaRecorder.VideoEncoder.H264) { + if (bitrate < profile.videoBitRate) { + bitrate = profile.videoBitRate; + } + break; + } + } + } + + if (bitrate == -1) { + Log.w(TAG, "Failed to find CamcorderProfile for w: " + width + "h: " + height + + " fps: " + + frameRate); + bitrate = getDefaultBitrate(width, height, frameRate); + } + Log.d(TAG, "Using bitrate " + bitrate + " for " + width + " " + height + " " + + frameRate); + return bitrate; + } + + /** * Retrieves the audio track format to be used for transcoding. * * @return the audio track format to be used if transcoding should be performed, and diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 3b4823e799ce..a7396faf7677 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -85,6 +85,8 @@ static const char SYSTEM_TIME_DIR_NAME[] = "time"; static const char SYSTEM_TIME_DIR_PATH[] = "/data/system/time"; static const char CLOCK_FONT_ASSET[] = "images/clock_font.png"; static const char CLOCK_FONT_ZIP_NAME[] = "clock_font.png"; +static const char PROGRESS_FONT_ASSET[] = "images/progress_font.png"; +static const char PROGRESS_FONT_ZIP_NAME[] = "progress_font.png"; static const char LAST_TIME_CHANGED_FILE_NAME[] = "last_time_change"; static const char LAST_TIME_CHANGED_FILE_PATH[] = "/data/system/time/last_time_change"; static const char ACCURATE_TIME_FLAG_FILE_NAME[] = "time_is_accurate"; @@ -100,6 +102,7 @@ static constexpr size_t FONT_NUM_ROWS = FONT_NUM_CHARS / FONT_NUM_COLS; static const int TEXT_CENTER_VALUE = INT_MAX; static const int TEXT_MISSING_VALUE = INT_MIN; static const char EXIT_PROP_NAME[] = "service.bootanim.exit"; +static const char PROGRESS_PROP_NAME[] = "service.bootanim.progress"; static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays"; static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1; static constexpr size_t TEXT_POS_LEN_MAX = 16; @@ -914,6 +917,18 @@ void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos) drawText(out, font, false, &x, &y); } +void BootAnimation::drawProgress(int percent, const Font& font, const int xPos, const int yPos) { + static constexpr int PERCENT_LENGTH = 5; + + char percentBuff[PERCENT_LENGTH]; + // ';' has the ascii code just after ':', and the font resource contains '%' + // for that ascii code. + sprintf(percentBuff, "%d;", percent); + int x = xPos; + int y = yPos; + drawText(percentBuff, font, false, &x, &y); +} + bool BootAnimation::parseAnimationDesc(Animation& animation) { String8 desString; @@ -933,6 +948,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) { int height = 0; int count = 0; int pause = 0; + int progress = 0; int framesToFadeCount = 0; char path[ANIM_ENTRY_NAME_MAX]; char color[7] = "000000"; // default to black if unspecified @@ -942,11 +958,17 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) { int nextReadPos; - if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) { - // SLOGD("> w=%d, h=%d, fps=%d", width, height, fps); + int topLineNumbers = sscanf(l, "%d %d %d %d", &width, &height, &fps, &progress); + if (topLineNumbers == 3 || topLineNumbers == 4) { + // SLOGD("> w=%d, h=%d, fps=%d, progress=%d", width, height, fps, progress); animation.width = width; animation.height = height; animation.fps = fps; + if (topLineNumbers == 4) { + animation.progressEnabled = (progress != 0); + } else { + animation.progressEnabled = false; + } } else if (sscanf(l, "%c %d %d %" STRTO(ANIM_PATH_MAX) "s%n", &pathType, &count, &pause, path, &nextReadPos) >= 4) { if (pathType == 'f') { @@ -1023,6 +1045,14 @@ bool BootAnimation::preloadZip(Animation& animation) { continue; } + if (entryName == PROGRESS_FONT_ZIP_NAME) { + FileMap* map = zip->createEntryFileMap(entry); + if (map) { + animation.progressFont.map = map; + } + continue; + } + for (size_t j = 0; j < pcount; j++) { if (path == animation.parts[j].path) { uint16_t method; @@ -1154,6 +1184,8 @@ bool BootAnimation::movie() { mClockEnabled = clockFontInitialized; } + initFont(&mAnimation->progressFont, PROGRESS_FONT_ASSET); + if (mClockEnabled && !updateIsTimeAccurate()) { mTimeCheckThread = new TimeCheckThread(this); mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL); @@ -1189,6 +1221,7 @@ bool BootAnimation::playAnimation(const Animation& animation) { elapsedRealtime()); int fadedFramesCount = 0; + int lastDisplayedProgress = 0; for (size_t i=0 ; i<pcount ; i++) { const Animation::Part& part(animation.parts[i]); const size_t fcount = part.frames.size(); @@ -1214,6 +1247,12 @@ bool BootAnimation::playAnimation(const Animation& animation) { part.backgroundColor[2], 1.0f); + // For the last animation, if we have progress indicator from + // the system, display it. + int currentProgress = android::base::GetIntProperty(PROGRESS_PROP_NAME, 0); + bool displayProgress = animation.progressEnabled && + (i == (pcount -1)) && currentProgress != 0; + for (size_t j=0 ; j<fcount ; j++) { if (shouldStopPlayingPart(part, fadedFramesCount)) break; @@ -1271,6 +1310,23 @@ bool BootAnimation::playAnimation(const Animation& animation) { drawClock(animation.clockFont, part.clockPosX, part.clockPosY); } + if (displayProgress) { + int newProgress = android::base::GetIntProperty(PROGRESS_PROP_NAME, 0); + // In case the new progress jumped suddenly, still show an + // increment of 1. + if (lastDisplayedProgress != 100) { + // Artificially sleep 1/10th a second to slow down the animation. + usleep(100000); + if (lastDisplayedProgress < newProgress) { + lastDisplayedProgress++; + } + } + // Put the progress percentage right below the animation. + int posY = animation.height / 3; + int posX = TEXT_CENTER_VALUE; + drawProgress(lastDisplayedProgress, animation.progressFont, posX, posY); + } + handleViewport(frameDuration); eglSwapBuffers(mDisplay, mSurface); diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index 4699cfe2ac2d..07432a2168b1 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -98,11 +98,13 @@ public: int fps; int width; int height; + bool progressEnabled; Vector<Part> parts; String8 audioConf; String8 fileName; ZipFileRO* zip; Font clockFont; + Font progressFont; }; // All callbacks will be called from this class's internal thread. @@ -168,6 +170,7 @@ private: bool movie(); void drawText(const char* str, const Font& font, bool bold, int* x, int* y); void drawClock(const Font& font, const int xPos, const int yPos); + void drawProgress(int percent, const Font& font, const int xPos, const int yPos); void fadeFrame(int frameLeft, int frameBottom, int frameWidth, int frameHeight, const Animation::Part& part, int fadedFramesCount); bool validClock(const Animation::Part& part); diff --git a/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md index f9b83c957d5b..1678053c48d9 100644 --- a/cmds/bootanimation/FORMAT.md +++ b/cmds/bootanimation/FORMAT.md @@ -22,11 +22,14 @@ The `bootanimation.zip` archive file includes: The first line defines the general parameters of the animation: - WIDTH HEIGHT FPS + WIDTH HEIGHT FPS [PROGRESS] * **WIDTH:** animation width (pixels) * **HEIGHT:** animation height (pixels) * **FPS:** frames per second, e.g. 60 + * **PROGRESS:** whether to show a progress percentage on the last part + + The percentage will be displayed with an x-coordinate of 'c', and a + y-coordinate set to 1/3 of the animation height. It is followed by a number of rows of the form: @@ -77,6 +80,11 @@ The file used to draw the time on top of the boot animation. The font format is * Each row is divided in half: regular weight glyphs on the top half, bold glyphs on the bottom * For a NxM image each character glyph will be N/16 pixels wide and M/(12*2) pixels high +## progress_font.png + +The file used to draw the boot progress in percentage on top of the boot animation. The font format +follows the same specification as the one described for clock_font.png. + ## loading and playing frames Each part is scanned and loaded directly from the zip archive. Within a part directory, every file diff --git a/cmds/idmap2/idmap2/CommandUtils.cpp b/cmds/idmap2/idmap2/CommandUtils.cpp index 8f5845bf2e53..09867f3a9c20 100644 --- a/cmds/idmap2/idmap2/CommandUtils.cpp +++ b/cmds/idmap2/idmap2/CommandUtils.cpp @@ -29,8 +29,8 @@ using android::idmap2::Result; using android::idmap2::Unit; Result<Unit> Verify(const std::string& idmap_path, const std::string& target_path, - const std::string& overlay_path, PolicyBitmask fulfilled_policies, - bool enforce_overlayable) { + const std::string& overlay_path, const std::string& overlay_name, + PolicyBitmask fulfilled_policies, bool enforce_overlayable) { SYSTRACE << "Verify " << idmap_path; std::ifstream fin(idmap_path); const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin); @@ -39,7 +39,7 @@ Result<Unit> Verify(const std::string& idmap_path, const std::string& target_pat return Error("failed to parse idmap header"); } - const auto header_ok = header->IsUpToDate(target_path.c_str(), overlay_path.c_str(), + const auto header_ok = header->IsUpToDate(target_path, overlay_path, overlay_name, fulfilled_policies, enforce_overlayable); if (!header_ok) { return Error(header_ok.GetError(), "idmap not up to date"); diff --git a/cmds/idmap2/idmap2/CommandUtils.h b/cmds/idmap2/idmap2/CommandUtils.h index e717e046d15d..e06896784d6a 100644 --- a/cmds/idmap2/idmap2/CommandUtils.h +++ b/cmds/idmap2/idmap2/CommandUtils.h @@ -20,10 +20,8 @@ #include "idmap2/PolicyUtils.h" #include "idmap2/Result.h" -android::idmap2::Result<android::idmap2::Unit> Verify(const std::string& idmap_path, - const std::string& target_path, - const std::string& overlay_path, - PolicyBitmask fulfilled_policies, - bool enforce_overlayable); +android::idmap2::Result<android::idmap2::Unit> Verify( + const std::string& idmap_path, const std::string& target_path, const std::string& overlay_path, + const std::string& overlay_name, PolicyBitmask fulfilled_policies, bool enforce_overlayable); #endif // IDMAP2_IDMAP2_COMMAND_UTILS_H_ diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp index 648b78e24c23..c93c717a15d2 100644 --- a/cmds/idmap2/idmap2/Create.cpp +++ b/cmds/idmap2/idmap2/Create.cpp @@ -50,6 +50,7 @@ Result<Unit> Create(const std::vector<std::string>& args) { std::string target_apk_path; std::string overlay_apk_path; std::string idmap_path; + std::string overlay_name; std::vector<std::string> policies; bool ignore_overlayable = false; @@ -62,9 +63,11 @@ Result<Unit> Create(const std::vector<std::string>& args) { "input: path to apk which contains the new resource values", &overlay_apk_path) .MandatoryOption("--idmap-path", "output: path to where to write idmap file", &idmap_path) + .OptionalOption("--overlay-name", "input: the value of android:name of the overlay", + &overlay_name) .OptionalOption("--policy", "input: an overlayable policy this overlay fulfills " - "(if none or supplied, the overlay policy will default to \"public\")", + "(if none are supplied, the overlay policy will default to \"public\")", &policies) .OptionalFlag("--ignore-overlayable", "disables overlayable and policy checks", &ignore_overlayable); @@ -100,8 +103,8 @@ Result<Unit> Create(const std::vector<std::string>& args) { return Error("failed to load apk %s", overlay_apk_path.c_str()); } - const auto idmap = - Idmap::FromApkAssets(*target_apk, *overlay_apk, fulfilled_policies, !ignore_overlayable); + const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, overlay_name, + fulfilled_policies, !ignore_overlayable); if (!idmap) { return Error(idmap.GetError(), "failed to create idmap"); } diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp index 19622c4ef65a..5db391caac30 100644 --- a/cmds/idmap2/idmap2/CreateMultiple.cpp +++ b/cmds/idmap2/idmap2/CreateMultiple.cpp @@ -105,7 +105,8 @@ Result<Unit> CreateMultiple(const std::vector<std::string>& args) { continue; } - if (!Verify(idmap_path, target_apk_path, overlay_apk_path, fulfilled_policies, + // TODO(b/175014391): Support multiple overlay tags in OverlayConfig + if (!Verify(idmap_path, target_apk_path, overlay_apk_path, "", fulfilled_policies, !ignore_overlayable)) { const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); if (!overlay_apk) { @@ -113,8 +114,8 @@ Result<Unit> CreateMultiple(const std::vector<std::string>& args) { continue; } - const auto idmap = - Idmap::FromApkAssets(*target_apk, *overlay_apk, fulfilled_policies, !ignore_overlayable); + const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, "", fulfilled_policies, + !ignore_overlayable); if (!idmap) { LOG(WARNING) << "failed to create idmap"; continue; diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp index 437180d3d1be..43a1951a5ba9 100644 --- a/cmds/idmap2/idmap2/Lookup.cpp +++ b/cmds/idmap2/idmap2/Lookup.cpp @@ -188,29 +188,27 @@ Result<Unit> Lookup(const std::vector<std::string>& args) { } if (i == 0) { - target_path = idmap_header->GetTargetPath().to_string(); + target_path = idmap_header->GetTargetPath(); auto target_apk = ApkAssets::Load(target_path); if (!target_apk) { return Error("failed to read target apk from %s", target_path.c_str()); } apk_assets.push_back(std::move(target_apk)); - auto manifest_info = ExtractOverlayManifestInfo(idmap_header->GetOverlayPath().to_string(), - true /* assert_overlay */); + auto manifest_info = ExtractOverlayManifestInfo(idmap_header->GetOverlayPath(), + idmap_header->GetOverlayName()); if (!manifest_info) { return manifest_info.GetError(); } - target_package_name = (*manifest_info).target_package; + target_package_name = manifest_info->target_package; } else if (target_path != idmap_header->GetTargetPath()) { return Error("different target APKs (expected target APK %s but %s has target APK %s)", - target_path.c_str(), idmap_path.c_str(), - idmap_header->GetTargetPath().to_string().c_str()); + target_path.c_str(), idmap_path.c_str(), idmap_header->GetTargetPath().c_str()); } auto overlay_apk = ApkAssets::LoadOverlay(idmap_path); if (!overlay_apk) { - return Error("failed to read overlay apk from %s", - idmap_header->GetOverlayPath().to_string().c_str()); + return Error("failed to read overlay apk from %s", idmap_header->GetOverlayPath().c_str()); } apk_assets.push_back(std::move(overlay_apk)); } diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index 15e22a3410cf..93537d32299b 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -155,8 +155,9 @@ Status Idmap2Service::verifyIdmap(const std::string& target_apk_path, return overlay_crc_status; } + // TODO(162841629): Support passing overlay name to idmap2d verify auto up_to_date = - header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), target_crc, overlay_crc, + header->IsUpToDate(target_apk_path, overlay_apk_path, "", target_crc, overlay_crc, ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable); *_aidl_return = static_cast<bool>(up_to_date); @@ -190,8 +191,9 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path, return error("failed to load apk " + overlay_apk_path); } + // TODO(162841629): Support passing overlay name to idmap2d create const auto idmap = - Idmap::FromApkAssets(*target_apk, *overlay_apk, policy_bitmask, enforce_overlayable); + Idmap::FromApkAssets(*target_apk, *overlay_apk, "", policy_bitmask, enforce_overlayable); if (!idmap) { return error(idmap.GetErrorMessage()); } diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h index bf31cbf8d4f7..5e189f2c1340 100644 --- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h +++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h @@ -39,7 +39,6 @@ class BinaryStreamVisitor : public Visitor { void Write8(uint8_t value); void Write16(uint16_t value); void Write32(uint32_t value); - void WriteString256(const StringPiece& value); void WriteString(const StringPiece& value); std::ostream& stream_; }; diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h index a35fad9d686c..1b815c1197ca 100644 --- a/cmds/idmap2/include/idmap2/Idmap.h +++ b/cmds/idmap2/include/idmap2/Idmap.h @@ -19,7 +19,8 @@ * * idmap := header data* * header := magic version target_crc overlay_crc fulfilled_policies - * enforce_overlayable target_path overlay_path debug_info + * enforce_overlayable target_path overlay_path overlay_name + * debug_info * data := data_header target_entry* target_inline_entry* overlay_entry* * string_pool * data_header := target_package_id overlay_package_id padding(2) target_entry_count @@ -37,13 +38,13 @@ * overlay_entry_count := <uint32_t> * overlay_id := <uint32_t> * overlay_package_id := <uint8_t> - * overlay_path := string256 + * overlay_name := string + * overlay_path := string * padding(n) := <uint8_t>[n] * Res_value::size := <uint16_t> * Res_value::type := <uint8_t> * Res_value::value := <uint32_t> * string := <uint32_t> <uint8_t>+ padding(n) - * string256 := <uint8_t>[256] * string_pool := string * string_pool_index := <uint32_t> * string_pool_length := <uint32_t> @@ -52,7 +53,7 @@ * target_inline_entry_count := <uint32_t> * target_id := <uint32_t> * target_package_id := <uint8_t> - * target_path := string256 + * target_path := string * value_type := <uint8_t> * value_data := <uint32_t> * version := <uint32_t> @@ -78,19 +79,12 @@ namespace android::idmap2 { class Idmap; class Visitor; -static constexpr const ResourceId kPadding = 0xffffffffu; -static constexpr const EntryId kNoEntry = 0xffffu; - // magic number: all idmap files start with this static constexpr const uint32_t kIdmapMagic = android::kIdmapMagic; // current version of the idmap binary format; must be incremented when the format is changed static constexpr const uint32_t kIdmapCurrentVersion = android::kIdmapCurrentVersion; -// strings in the idmap are encoded char arrays of length 'kIdmapStringLength' (including mandatory -// terminating null) -static constexpr const size_t kIdmapStringLength = 256; - // Retrieves a crc generated using all of the files within the zip that can affect idmap generation. Result<uint32_t> GetPackageCrc(const ZipFile& zip_info); @@ -122,32 +116,38 @@ class IdmapHeader { return enforce_overlayable_; } - inline StringPiece GetTargetPath() const { - return StringPiece(target_path_); + const std::string& GetTargetPath() const { + return target_path_; } - inline StringPiece GetOverlayPath() const { - return StringPiece(overlay_path_); + const std::string& GetOverlayPath() const { + return overlay_path_; } - inline const std::string& GetDebugInfo() const { + const std::string& GetOverlayName() const { + return overlay_name_; + } + + const std::string& GetDebugInfo() const { return debug_info_; } // Invariant: anytime the idmap data encoding is changed, the idmap version // field *must* be incremented. Because of this, we know that if the idmap // header is up-to-date the entire file is up-to-date. - Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path, - PolicyBitmask fulfilled_policies, bool enforce_overlayable) const; - Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path, uint32_t target_crc, + Result<Unit> IsUpToDate(const std::string& target_path, const std::string& overlay_path, + const std::string& overlay_name, PolicyBitmask fulfilled_policies, + bool enforce_overlayable) const; + + Result<Unit> IsUpToDate(const std::string& target_path, const std::string& overlay_path, + const std::string& overlay_name, uint32_t target_crc, uint32_t overlay_crc, PolicyBitmask fulfilled_policies, bool enforce_overlayable) const; void accept(Visitor* v) const; private: - IdmapHeader() { - } + IdmapHeader() = default; uint32_t magic_; uint32_t version_; @@ -155,8 +155,9 @@ class IdmapHeader { uint32_t overlay_crc_; uint32_t fulfilled_policies_; bool enforce_overlayable_; - char target_path_[kIdmapStringLength]; - char overlay_path_[kIdmapStringLength]; + std::string target_path_; + std::string overlay_path_; + std::string overlay_name_; std::string debug_info_; friend Idmap; @@ -251,8 +252,7 @@ class IdmapData { void accept(Visitor* v) const; private: - IdmapData() { - } + IdmapData() = default; std::unique_ptr<const Header> header_; std::vector<TargetEntry> target_entries_; @@ -277,22 +277,22 @@ class Idmap { // the target and overlay package names static Result<std::unique_ptr<const Idmap>> FromApkAssets(const ApkAssets& target_apk_assets, const ApkAssets& overlay_apk_assets, + const std::string& overlay_name, const PolicyBitmask& fulfilled_policies, bool enforce_overlayable); - inline const std::unique_ptr<const IdmapHeader>& GetHeader() const { + const std::unique_ptr<const IdmapHeader>& GetHeader() const { return header_; } - inline const std::vector<std::unique_ptr<const IdmapData>>& GetData() const { + const std::vector<std::unique_ptr<const IdmapData>>& GetData() const { return data_; } void accept(Visitor* v) const; private: - Idmap() { - } + Idmap() = default; std::unique_ptr<const IdmapHeader> header_; std::vector<std::unique_ptr<const IdmapData>> data_; @@ -302,8 +302,7 @@ class Idmap { class Visitor { public: - virtual ~Visitor() { - } + virtual ~Visitor() = default; virtual void visit(const Idmap& idmap) = 0; virtual void visit(const IdmapHeader& header) = 0; virtual void visit(const IdmapData& data) = 0; diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h index 58edc99715fd..45835164ef8e 100644 --- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h +++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h @@ -44,7 +44,9 @@ class RawPrintVisitor : public Visitor { void print(uint8_t value, const char* fmt, ...); void print(uint16_t value, const char* fmt, ...); void print(uint32_t value, const char* fmt, ...); - void print(const std::string& value, size_t encoded_size, const char* fmt, ...); + void print(const std::string& value, bool print_value, const char* fmt, ...); + void align(); + void pad(size_t padding); std::ostream& stream_; std::vector<std::unique_ptr<const ApkAssets>> apk_assets_; diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h index c643b0e8800c..cd14d3e7254c 100644 --- a/cmds/idmap2/include/idmap2/ResourceUtils.h +++ b/cmds/idmap2/include/idmap2/ResourceUtils.h @@ -44,17 +44,14 @@ bool IsReference(uint8_t data_type); StringPiece DataTypeToString(uint8_t data_type); struct OverlayManifestInfo { - std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes) - std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes) - std::string requiredSystemPropertyName; // NOLINT(misc-non-private-member-variables-in-classes) - std::string requiredSystemPropertyValue; // NOLINT(misc-non-private-member-variables-in-classes) - uint32_t resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes) - bool is_static; // NOLINT(misc-non-private-member-variables-in-classes) - int priority = -1; // NOLINT(misc-non-private-member-variables-in-classes) + std::string name; // NOLINT(misc-non-private-member-variables-in-classes) + std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes) + std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes) + uint32_t resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes) }; Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path, - bool assert_overlay = true); + const std::string& name); Result<std::string> ResToTypeEntryName(const AssetManager2& am, ResourceId resid); diff --git a/cmds/idmap2/include/idmap2/XmlParser.h b/cmds/idmap2/include/idmap2/XmlParser.h index 972a6d7e3427..1c74ab3bb691 100644 --- a/cmds/idmap2/include/idmap2/XmlParser.h +++ b/cmds/idmap2/include/idmap2/XmlParser.h @@ -22,6 +22,7 @@ #include <memory> #include <string> +#include "ResourceUtils.h" #include "Result.h" #include "android-base/macros.h" #include "androidfw/ResourceTypes.h" @@ -39,8 +40,11 @@ class XmlParser { Event event() const; std::string name() const; - Result<std::string> GetAttributeStringValue(const std::string& name) const; Result<Res_value> GetAttributeValue(const std::string& name) const; + Result<Res_value> GetAttributeValue(ResourceId attr, const std::string& label) const; + + Result<std::string> GetAttributeStringValue(const std::string& name) const; + Result<std::string> GetAttributeStringValue(ResourceId attr, const std::string& label) const; bool operator==(const Node& rhs) const; bool operator!=(const Node& rhs) const; diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp index 726f6c5c2c99..c16310792d12 100644 --- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp +++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp @@ -38,13 +38,6 @@ void BinaryStreamVisitor::Write32(uint32_t value) { stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t)); } -void BinaryStreamVisitor::WriteString256(const StringPiece& value) { - char buf[kIdmapStringLength]; - memset(buf, 0, sizeof(buf)); - memcpy(buf, value.data(), std::min(value.size(), sizeof(buf))); - stream_.write(buf, sizeof(buf)); -} - void BinaryStreamVisitor::WriteString(const StringPiece& value) { // pad with null to nearest word boundary; size_t padding_size = CalculatePadding(value.size()); @@ -64,8 +57,9 @@ void BinaryStreamVisitor::visit(const IdmapHeader& header) { Write32(header.GetOverlayCrc()); Write32(header.GetFulfilledPolicies()); Write32(static_cast<uint8_t>(header.GetEnforceOverlayable())); - WriteString256(header.GetTargetPath()); - WriteString256(header.GetOverlayPath()); + WriteString(header.GetTargetPath()); + WriteString(header.GetOverlayPath()); + WriteString(header.GetOverlayName()); WriteString(header.GetDebugInfo()); } diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp index 1129413584b2..5af84b0c6e4e 100644 --- a/cmds/idmap2/libidmap2/Idmap.cpp +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -69,38 +69,26 @@ bool WARN_UNUSED Read32(std::istream& stream, uint32_t* out) { return false; } -// a string is encoded as a kIdmapStringLength char array; the array is always null-terminated -bool WARN_UNUSED ReadString256(std::istream& stream, char out[kIdmapStringLength]) { - char buf[kIdmapStringLength]; - memset(buf, 0, sizeof(buf)); - if (!stream.read(buf, sizeof(buf))) { - return false; - } - if (buf[sizeof(buf) - 1] != '\0') { - return false; - } - memcpy(out, buf, sizeof(buf)); - return true; -} - -Result<std::string> ReadString(std::istream& stream) { +bool WARN_UNUSED ReadString(std::istream& stream, std::string* out) { uint32_t size; if (!Read32(stream, &size)) { - return Error("failed to read string size"); + return false; } if (size == 0) { - return std::string(""); + *out = ""; + return true; } std::string buf(size, '\0'); if (!stream.read(buf.data(), size)) { - return Error("failed to read string of size %u", size); + return false; } uint32_t padding_size = CalculatePadding(size); std::string padding(padding_size, '\0'); if (!stream.read(padding.data(), padding_size)) { - return Error("failed to read string padding of size %u", padding_size); + return false; } - return buf; + *out = buf; + return true; } } // namespace @@ -119,28 +107,25 @@ std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& s if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) || !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) || !Read32(stream, &idmap_header->fulfilled_policies_) || - !Read32(stream, &enforce_overlayable) || !ReadString256(stream, idmap_header->target_path_) || - !ReadString256(stream, idmap_header->overlay_path_)) { + !Read32(stream, &enforce_overlayable) || !ReadString(stream, &idmap_header->target_path_) || + !ReadString(stream, &idmap_header->overlay_path_) || + !ReadString(stream, &idmap_header->overlay_name_) || + !ReadString(stream, &idmap_header->debug_info_)) { return nullptr; } idmap_header->enforce_overlayable_ = enforce_overlayable != 0U; - - auto debug_str = ReadString(stream); - if (!debug_str) { - return nullptr; - } - idmap_header->debug_info_ = std::move(*debug_str); - return std::move(idmap_header); } -Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path, +Result<Unit> IdmapHeader::IsUpToDate(const std::string& target_path, + const std::string& overlay_path, + const std::string& overlay_name, PolicyBitmask fulfilled_policies, bool enforce_overlayable) const { const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path); if (!target_zip) { - return Error("failed to open target %s", target_path); + return Error("failed to open target %s", target_path.c_str()); } const Result<uint32_t> target_crc = GetPackageCrc(*target_zip); @@ -150,7 +135,7 @@ Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overla const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path); if (!overlay_zip) { - return Error("failed to overlay target %s", overlay_path); + return Error("failed to overlay target %s", overlay_path.c_str()); } const Result<uint32_t> overlay_crc = GetPackageCrc(*overlay_zip); @@ -158,13 +143,14 @@ Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overla return Error("failed to get overlay crc"); } - return IsUpToDate(target_path, overlay_path, *target_crc, *overlay_crc, fulfilled_policies, - enforce_overlayable); + return IsUpToDate(target_path, overlay_path, overlay_name, *target_crc, *overlay_crc, + fulfilled_policies, enforce_overlayable); } -Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path, - uint32_t target_crc, uint32_t overlay_crc, - PolicyBitmask fulfilled_policies, +Result<Unit> IdmapHeader::IsUpToDate(const std::string& target_path, + const std::string& overlay_path, + const std::string& overlay_name, uint32_t target_crc, + uint32_t overlay_crc, PolicyBitmask fulfilled_policies, bool enforce_overlayable) const { if (magic_ != kIdmapMagic) { return Error("bad magic: actual 0x%08x, expected 0x%08x", magic_, kIdmapMagic); @@ -194,14 +180,19 @@ Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overla enforce_overlayable ? "true" : "false", enforce_overlayable_ ? "true" : "false"); } - if (strcmp(target_path, target_path_) != 0) { - return Error("bad target path: idmap version %s, file system version %s", target_path, - target_path_); + if (target_path != target_path_) { + return Error("bad target path: idmap version %s, file system version %s", target_path.c_str(), + target_path_.c_str()); + } + + if (overlay_path != overlay_path_) { + return Error("bad overlay path: idmap version %s, file system version %s", overlay_path.c_str(), + overlay_path_.c_str()); } - if (strcmp(overlay_path, overlay_path_) != 0) { - return Error("bad overlay path: idmap version %s, file system version %s", overlay_path, - overlay_path_); + if (overlay_name != overlay_name_) { + return Error("bad overlay name: idmap version %s, file system version %s", overlay_name.c_str(), + overlay_name_.c_str()); } return Unit{}; @@ -262,12 +253,9 @@ std::unique_ptr<const IdmapData> IdmapData::FromBinaryStream(std::istream& strea } // Read raw string pool bytes. - auto string_pool_data = ReadString(stream); - if (!string_pool_data) { + if (!ReadString(stream, &data->string_pool_data_)) { return nullptr; } - data->string_pool_data_ = std::move(*string_pool_data); - return std::move(data); } @@ -337,6 +325,7 @@ Result<std::unique_ptr<const IdmapData>> IdmapData::FromResourceMapping( Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const ApkAssets& target_apk_assets, const ApkAssets& overlay_apk_assets, + const std::string& overlay_name, const PolicyBitmask& fulfilled_policies, bool enforce_overlayable) { SYSTRACE << "Idmap::FromApkAssets"; @@ -368,32 +357,20 @@ Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const ApkAssets& targe return Error(crc.GetError(), "failed to get zip CRC for overlay"); } header->overlay_crc_ = *crc; - header->fulfilled_policies_ = fulfilled_policies; header->enforce_overlayable_ = enforce_overlayable; + header->target_path_ = target_apk_path; + header->overlay_path_ = overlay_apk_path; + header->overlay_name_ = overlay_name; - if (target_apk_path.size() > sizeof(header->target_path_)) { - return Error("target apk path \"%s\" longer than maximum size %zu", target_apk_path.c_str(), - sizeof(header->target_path_)); - } - memset(header->target_path_, 0, sizeof(header->target_path_)); - memcpy(header->target_path_, target_apk_path.data(), target_apk_path.size()); - - if (overlay_apk_path.size() > sizeof(header->overlay_path_)) { - return Error("overlay apk path \"%s\" longer than maximum size %zu", overlay_apk_path.c_str(), - sizeof(header->target_path_)); - } - memset(header->overlay_path_, 0, sizeof(header->overlay_path_)); - memcpy(header->overlay_path_, overlay_apk_path.data(), overlay_apk_path.size()); - - auto overlay_info = utils::ExtractOverlayManifestInfo(overlay_apk_path); - if (!overlay_info) { - return overlay_info.GetError(); + auto info = utils::ExtractOverlayManifestInfo(overlay_apk_path, overlay_name); + if (!info) { + return info.GetError(); } LogInfo log_info; auto resource_mapping = - ResourceMapping::FromApkAssets(target_apk_assets, overlay_apk_assets, *overlay_info, + ResourceMapping::FromApkAssets(target_apk_assets, overlay_apk_assets, *info, fulfilled_policies, enforce_overlayable, log_info); if (!resource_mapping) { return resource_mapping.GetError(); diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp index 3037a791328e..7e090a983f95 100644 --- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp +++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp @@ -39,6 +39,10 @@ void PrettyPrintVisitor::visit(const IdmapHeader& header) { << TAB "target apk path : " << header.GetTargetPath() << std::endl << TAB "overlay apk path : " << header.GetOverlayPath() << std::endl; + if (!header.GetOverlayName().empty()) { + stream_ << "Overlay name: " << header.GetOverlayName() << std::endl; + } + const std::string& debug = header.GetDebugInfo(); if (!debug.empty()) { std::istringstream debug_stream(debug); @@ -49,12 +53,12 @@ void PrettyPrintVisitor::visit(const IdmapHeader& header) { } } - if (auto target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string())) { + if (auto target_apk_ = ApkAssets::Load(header.GetTargetPath())) { target_am_.SetApkAssets({target_apk_.get()}); apk_assets_.push_back(std::move(target_apk_)); } - if (auto overlay_apk = ApkAssets::Load(header.GetOverlayPath().to_string())) { + if (auto overlay_apk = ApkAssets::Load(header.GetOverlayPath())) { overlay_am_.SetApkAssets({overlay_apk.get()}); apk_assets_.push_back(std::move(overlay_apk)); } diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp index 82f5d26cbbb3..b517aa3a0c01 100644 --- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp +++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp @@ -43,20 +43,18 @@ void RawPrintVisitor::visit(const IdmapHeader& header) { print(header.GetFulfilledPolicies(), "fulfilled policies: %s", PoliciesToDebugString(header.GetFulfilledPolicies()).c_str()); print(static_cast<uint32_t>(header.GetEnforceOverlayable()), "enforce overlayable"); - print(header.GetTargetPath().to_string(), kIdmapStringLength, "target path"); - print(header.GetOverlayPath().to_string(), kIdmapStringLength, "overlay path"); + print(header.GetTargetPath(), true /* print_value */, "target path"); + print(header.GetOverlayPath(), true /* print_value */, "overlay path"); + print(header.GetOverlayName(), true /* print_value */, "overlay name"); + print(header.GetDebugInfo(), false /* print_value */, "debug info"); - uint32_t debug_info_size = header.GetDebugInfo().size(); - print(debug_info_size, "debug info size"); - print("...", debug_info_size + CalculatePadding(debug_info_size), "debug info"); - - auto target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string()); + auto target_apk_ = ApkAssets::Load(header.GetTargetPath()); if (target_apk_) { target_am_.SetApkAssets({target_apk_.get()}); apk_assets_.push_back(std::move(target_apk_)); } - auto overlay_apk_ = ApkAssets::Load(header.GetOverlayPath().to_string()); + auto overlay_apk_ = ApkAssets::Load(header.GetOverlayPath()); if (overlay_apk_) { overlay_am_.SetApkAssets({overlay_apk_.get()}); apk_assets_.push_back(std::move(overlay_apk_)); @@ -100,7 +98,7 @@ void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { print(target_entry.target_id, "target id"); } - print("...", sizeof(Res_value::size) + sizeof(Res_value::res0), "padding"); + pad(sizeof(Res_value::size) + sizeof(Res_value::res0)); print(target_entry.value.data_type, "type: %s", utils::DataTypeToString(target_entry.value.data_type).data()); @@ -143,15 +141,13 @@ void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { } } - uint32_t string_pool_size = data.GetStringPoolData().size(); - print(string_pool_size, "string pool size"); - print("...", string_pool_size + CalculatePadding(string_pool_size), "string pool"); + print(data.GetStringPoolData(), false /* print_value */, "string pool"); } void RawPrintVisitor::visit(const IdmapData::Header& header) { print(header.GetTargetPackageId(), "target package id"); print(header.GetOverlayPackageId(), "overlay package id"); - print("...", sizeof(Idmap_data_header::p0), "padding"); + align(); print(header.GetTargetEntryCount(), "target entry count"); print(header.GetTargetInlineEntryCount(), "target inline entry count"); print(header.GetOverlayEntryCount(), "overlay entry count"); @@ -168,7 +164,6 @@ void RawPrintVisitor::print(uint8_t value, const char* fmt, ...) { stream_ << base::StringPrintf("%08zx: %02x", offset_, value) << " " << comment << std::endl; - offset_ += sizeof(uint8_t); } @@ -181,7 +176,6 @@ void RawPrintVisitor::print(uint16_t value, const char* fmt, ...) { va_end(ap); stream_ << base::StringPrintf("%08zx: %04x", offset_, value) << " " << comment << std::endl; - offset_ += sizeof(uint16_t); } @@ -194,22 +188,35 @@ void RawPrintVisitor::print(uint32_t value, const char* fmt, ...) { va_end(ap); stream_ << base::StringPrintf("%08zx: %08x", offset_, value) << " " << comment << std::endl; - offset_ += sizeof(uint32_t); } // NOLINTNEXTLINE(cert-dcl50-cpp) -void RawPrintVisitor::print(const std::string& value, size_t encoded_size, const char* fmt, ...) { +void RawPrintVisitor::print(const std::string& value, bool print_value, const char* fmt, ...) { va_list ap; va_start(ap, fmt); std::string comment; base::StringAppendV(&comment, fmt, ap); va_end(ap); - stream_ << base::StringPrintf("%08zx: ", offset_) << "........ " << comment << ": " << value - << std::endl; + stream_ << base::StringPrintf("%08zx: %08x", offset_, (uint32_t)value.size()) << " " << comment + << " size" << std::endl; + offset_ += sizeof(uint32_t); - offset_ += encoded_size; + stream_ << base::StringPrintf("%08zx: ", offset_) << "........ " << comment; + offset_ += value.size() + CalculatePadding(value.size()); + + if (print_value) { + stream_ << ": " << value; + } + stream_ << std::endl; } +void RawPrintVisitor::align() { + offset_ += CalculatePadding(offset_); +} + +void RawPrintVisitor::pad(size_t padding) { + offset_ += padding; +} } // namespace android::idmap2 diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp index d777cbfa9a14..9abb9e458902 100644 --- a/cmds/idmap2/libidmap2/ResourceMapping.cpp +++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp @@ -230,8 +230,8 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy( base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str()); auto target_resource_result = target_am->GetResourceId(full_name); if (!target_resource_result.has_value()) { - log_info.Warning(LogMessage() << "failed to find resource \"" << full_name - << "\" in target resources"); + log_info.Warning(LogMessage() + << "failed to find resource \"" << full_name << "\" in target resources"); continue; } diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp index e817140238ae..4e85e5751300 100644 --- a/cmds/idmap2/libidmap2/ResourceUtils.cpp +++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp @@ -32,6 +32,12 @@ using android::idmap2::ZipFile; using android::util::Utf16ToUtf8; namespace android::idmap2::utils { +namespace { +constexpr ResourceId kAttrName = 0x01010003; +constexpr ResourceId kAttrResourcesMap = 0x01010609; +constexpr ResourceId kAttrTargetName = 0x0101044d; +constexpr ResourceId kAttrTargetPackage = 0x01010021; +} // namespace bool IsReference(uint8_t data_type) { return data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE; @@ -92,7 +98,7 @@ Result<std::string> ResToTypeEntryName(const AssetManager2& am, uint32_t resid) } Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path, - bool assert_overlay) { + const std::string& name) { std::unique_ptr<const ZipFile> zip = ZipFile::Open(path); if (!zip) { return Error("failed to open %s as a zip file", path.c_str()); @@ -113,65 +119,49 @@ Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path, return Error("root element tag is not <manifest> in AndroidManifest.xml of %s", path.c_str()); } - auto overlay_it = std::find_if(manifest_it.begin(), manifest_it.end(), [](const auto& it) { - return it.event() == XmlParser::Event::START_TAG && it.name() == "overlay"; - }); - - OverlayManifestInfo info{}; - if (overlay_it == manifest_it.end()) { - if (!assert_overlay) { - return info; + for (auto&& it : manifest_it) { + if (it.event() != XmlParser::Event::START_TAG || it.name() != "overlay") { + continue; } - return Error("<overlay> missing from AndroidManifest.xml of %s", path.c_str()); - } - - if (auto result_str = overlay_it->GetAttributeStringValue("targetPackage")) { - info.target_package = *result_str; - } else { - return Error("android:targetPackage missing from <overlay> of %s: %s", path.c_str(), - result_str.GetErrorMessage().c_str()); - } - - if (auto result_str = overlay_it->GetAttributeStringValue("targetName")) { - info.target_name = *result_str; - } - if (auto result_value = overlay_it->GetAttributeValue("resourcesMap")) { - if (IsReference((*result_value).dataType)) { - info.resource_mapping = (*result_value).data; - } else { - return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s", - path.c_str()); + OverlayManifestInfo info{}; + if (auto result_str = it.GetAttributeStringValue(kAttrName, "android:name")) { + if (*result_str != name) { + // A value for android:name was found, but either a the name does not match the requested + // name, or an <overlay> tag with no name was requested. + continue; + } + info.name = *result_str; + } else if (!name.empty()) { + // This tag does not have a value for android:name, but an <overlay> tag with a specific name + // has been requested. + continue; } - } - if (auto result_value = overlay_it->GetAttributeValue("isStatic")) { - if ((*result_value).dataType >= Res_value::TYPE_FIRST_INT && - (*result_value).dataType <= Res_value::TYPE_LAST_INT) { - info.is_static = (*result_value).data != 0U; + if (auto result_str = it.GetAttributeStringValue(kAttrTargetPackage, "android:targetPackage")) { + info.target_package = *result_str; } else { - return Error("android:isStatic is not a boolean in AndroidManifest.xml of %s", path.c_str()); + return Error("android:targetPackage missing from <overlay> of %s: %s", path.c_str(), + result_str.GetErrorMessage().c_str()); } - } - if (auto result_value = overlay_it->GetAttributeValue("priority")) { - if ((*result_value).dataType >= Res_value::TYPE_FIRST_INT && - (*result_value).dataType <= Res_value::TYPE_LAST_INT) { - info.priority = (*result_value).data; - } else { - return Error("android:priority is not an integer in AndroidManifest.xml of %s", path.c_str()); + if (auto result_str = it.GetAttributeStringValue(kAttrTargetName, "android:targetName")) { + info.target_name = *result_str; } - } - if (auto result_str = overlay_it->GetAttributeStringValue("requiredSystemPropertyName")) { - info.requiredSystemPropertyName = *result_str; - } - - if (auto result_str = overlay_it->GetAttributeStringValue("requiredSystemPropertyValue")) { - info.requiredSystemPropertyValue = *result_str; + if (auto result_value = it.GetAttributeValue(kAttrResourcesMap, "android:resourcesMap")) { + if (IsReference((*result_value).dataType)) { + info.resource_mapping = (*result_value).data; + } else { + return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s", + path.c_str()); + } + } + return info; } - return info; + return Error("<overlay> with android:name \"%s\" missing from AndroidManifest.xml of %s", + name.c_str(), path.c_str()); } } // namespace android::idmap2::utils diff --git a/cmds/idmap2/libidmap2/XmlParser.cpp b/cmds/idmap2/libidmap2/XmlParser.cpp index 4030b83b3a41..00baea46f909 100644 --- a/cmds/idmap2/libidmap2/XmlParser.cpp +++ b/cmds/idmap2/libidmap2/XmlParser.cpp @@ -90,15 +90,27 @@ std::string XmlParser::Node::name() const { return String8(key16).c_str(); } -Result<std::string> XmlParser::Node::GetAttributeStringValue(const std::string& name) const { - auto value = GetAttributeValue(name); - if (!value) { - return value.GetError(); +template <typename Func> +Result<Res_value> FindAttribute(const ResXMLParser& parser, const std::string& label, + Func&& predicate) { + for (size_t i = 0; i < parser.getAttributeCount(); i++) { + if (!predicate(i)) { + continue; + } + Res_value res_value{}; + if (parser.getAttributeValue(i, &res_value) == BAD_TYPE) { + return Error(R"(Bad value for attribute "%s")", label.c_str()); + } + return res_value; } + return Error(R"(Failed to find attribute "%s")", label.c_str()); +} - switch ((*value).dataType) { +Result<std::string> GetStringValue(const ResXMLParser& parser, const Res_value& value, + const std::string& label) { + switch (value.dataType) { case Res_value::TYPE_STRING: { - if (auto str = parser_.getStrings().string8ObjectAt((*value).data); str.ok()) { + if (auto str = parser.getStrings().string8ObjectAt(value.data); str.ok()) { return std::string(str->string()); } break; @@ -106,31 +118,37 @@ Result<std::string> XmlParser::Node::GetAttributeStringValue(const std::string& case Res_value::TYPE_INT_DEC: case Res_value::TYPE_INT_HEX: case Res_value::TYPE_INT_BOOLEAN: { - return std::to_string((*value).data); + return std::to_string(value.data); } } + return Error(R"(Failed to convert attribute "%s" value to a string)", label.c_str()); +} - return Error(R"(Failed to convert attribute "%s" value to a string)", name.c_str()); +Result<Res_value> XmlParser::Node::GetAttributeValue(ResourceId attr, + const std::string& label) const { + return FindAttribute(parser_, label, [&](size_t index) -> bool { + return parser_.getAttributeNameResID(index) == attr; + }); } Result<Res_value> XmlParser::Node::GetAttributeValue(const std::string& name) const { - size_t len; - for (size_t i = 0; i < parser_.getAttributeCount(); i++) { - const String16 key16(parser_.getAttributeName(i, &len)); + return FindAttribute(parser_, name, [&](size_t index) -> bool { + size_t len; + const String16 key16(parser_.getAttributeName(index, &len)); std::string key = String8(key16).c_str(); - if (key != name) { - continue; - } - - Res_value res_value{}; - if (parser_.getAttributeValue(i, &res_value) == BAD_TYPE) { - return Error(R"(Bad value for attribute "%s")", name.c_str()); - } + return key == name; + }); +} - return res_value; - } +Result<std::string> XmlParser::Node::GetAttributeStringValue(ResourceId attr, + const std::string& label) const { + auto value = GetAttributeValue(attr, label); + return value ? GetStringValue(parser_, *value, label) : value.GetError(); +} - return Error(R"(Failed to find attribute "%s")", name.c_str()); +Result<std::string> XmlParser::Node::GetAttributeStringValue(const std::string& name) const { + auto value = GetAttributeValue(name); + return value ? GetStringValue(parser_, *value, name) : value.GetError(); } Result<std::unique_ptr<const XmlParser>> XmlParser::Create(const void* data, size_t size, diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp index c3a3e0ba9047..524aabcec652 100644 --- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp +++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp @@ -33,7 +33,7 @@ using ::testing::NotNull; namespace android::idmap2 { TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) { - std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen); std::istringstream raw_stream(raw); auto result1 = Idmap::FromBinaryStream(raw_stream); diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp index e7e9e4cf5091..a55b41b83c93 100644 --- a/cmds/idmap2/tests/Idmap2BinaryTests.cpp +++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp @@ -35,6 +35,7 @@ #include <vector> #include "R.h" +#include "TestConstants.h" #include "TestHelpers.h" #include "androidfw/PosixUtils.h" #include "gmock/gmock.h" @@ -43,6 +44,7 @@ #include "idmap2/Idmap.h" #include "private/android_filesystem_config.h" +using ::android::base::StringPrintf; using ::android::util::ExecuteBinary; using ::testing::NotNull; @@ -90,6 +92,7 @@ TEST_F(Idmap2BinaryTests, Create) { "create", "--target-apk-path", GetTargetApkPath(), "--overlay-apk-path", GetOverlayApkPath(), + "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT, "--idmap-path", GetIdmapPath()}); // clang-format on ASSERT_THAT(result, NotNull()); @@ -116,6 +119,7 @@ TEST_F(Idmap2BinaryTests, Dump) { "create", "--target-apk-path", GetTargetApkPath(), "--overlay-apk-path", GetOverlayApkPath(), + "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT, "--idmap-path", GetIdmapPath()}); // clang-format on ASSERT_THAT(result, NotNull()); @@ -128,14 +132,23 @@ TEST_F(Idmap2BinaryTests, Dump) { // clang-format on ASSERT_THAT(result, NotNull()); ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; - ASSERT_NE(result->stdout.find(R::target::integer::literal::int1 + " -> 0x7f010000"), - std::string::npos); - ASSERT_NE(result->stdout.find(R::target::string::literal::str1 + " -> 0x7f020000"), - std::string::npos); - ASSERT_NE(result->stdout.find(R::target::string::literal::str3 + " -> 0x7f020001"), - std::string::npos); - ASSERT_NE(result->stdout.find(R::target::string::literal::str4 + " -> 0x7f020002"), - std::string::npos); + + ASSERT_NE(result->stdout.find(StringPrintf("0x%08x -> 0x%08x", R::target::integer::int1, + R::overlay::integer::int1)), + std::string::npos) + << result->stdout; + ASSERT_NE(result->stdout.find(StringPrintf("0x%08x -> 0x%08x", R::target::string::str1, + R::overlay::string::str1)), + std::string::npos) + << result->stdout; + ASSERT_NE(result->stdout.find(StringPrintf("0x%08x -> 0x%08x", R::target::string::str3, + R::overlay::string::str3)), + std::string::npos) + << result->stdout; + ASSERT_NE(result->stdout.find(StringPrintf("0x%08x -> 0x%08x", R::target::string::str4, + R::overlay::string::str4)), + std::string::npos) + << result->stdout; // clang-format off result = ExecuteBinary({"idmap2", @@ -167,6 +180,7 @@ TEST_F(Idmap2BinaryTests, Lookup) { "create", "--target-apk-path", GetTargetApkPath(), "--overlay-apk-path", GetOverlayApkPath(), + "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT, "--idmap-path", GetIdmapPath()}); // clang-format on ASSERT_THAT(result, NotNull()); @@ -177,7 +191,7 @@ TEST_F(Idmap2BinaryTests, Lookup) { "lookup", "--idmap-path", GetIdmapPath(), "--config", "", - "--resid", R::target::string::literal::str1}); + "--resid", StringPrintf("0x%08x", R::target::string::str1)}); // clang-format on ASSERT_THAT(result, NotNull()); ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; @@ -229,6 +243,7 @@ TEST_F(Idmap2BinaryTests, InvalidCommandLineOptions) { "create", "--target-apk-path", GetTargetApkPath(), "--overlay-apk-path", GetOverlayApkPath(), + "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT, "--idmap-path"}); // clang-format on ASSERT_THAT(result, NotNull()); @@ -240,6 +255,7 @@ TEST_F(Idmap2BinaryTests, InvalidCommandLineOptions) { "create", "--target-apk-path", invalid_target_apk_path, "--overlay-apk-path", GetOverlayApkPath(), + "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT, "--idmap-path", GetIdmapPath()}); // clang-format on ASSERT_THAT(result, NotNull()); @@ -251,6 +267,7 @@ TEST_F(Idmap2BinaryTests, InvalidCommandLineOptions) { "create", "--target-apk-path", GetTargetApkPath(), "--overlay-apk-path", GetOverlayApkPath(), + "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT, "--idmap-path", GetIdmapPath(), "--policy", "this-does-not-exist"}); // clang-format on diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp index 9b42a2781b58..c13b049eadfe 100644 --- a/cmds/idmap2/tests/IdmapTests.cpp +++ b/cmds/idmap2/tests/IdmapTests.cpp @@ -27,6 +27,7 @@ #include "TestHelpers.h" #include "android-base/macros.h" #include "androidfw/ApkAssets.h" +#include "androidfw/ResourceUtils.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "idmap2/BinaryStreamVisitor.h" @@ -35,7 +36,6 @@ #include "idmap2/LogInfo.h" using android::Res_value; -using ::testing::IsNull; using ::testing::NotNull; using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; @@ -61,36 +61,25 @@ TEST(IdmapTests, TestCanonicalIdmapPathFor) { } TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) { - std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen); std::istringstream stream(raw); std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); ASSERT_THAT(header, NotNull()); ASSERT_EQ(header->GetMagic(), 0x504d4449U); - ASSERT_EQ(header->GetVersion(), 0x05U); + ASSERT_EQ(header->GetVersion(), 0x07U); ASSERT_EQ(header->GetTargetCrc(), 0x1234U); ASSERT_EQ(header->GetOverlayCrc(), 0x5678U); ASSERT_EQ(header->GetFulfilledPolicies(), 0x11); ASSERT_EQ(header->GetEnforceOverlayable(), true); - ASSERT_EQ(header->GetTargetPath().to_string(), "targetX.apk"); - ASSERT_EQ(header->GetOverlayPath().to_string(), "overlayX.apk"); + ASSERT_EQ(header->GetTargetPath(), "targetX.apk"); + ASSERT_EQ(header->GetOverlayPath(), "overlayX.apk"); ASSERT_EQ(header->GetDebugInfo(), "debug"); } -TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) { - std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); - // overwrite the target path string, including the terminating null, with '.' - for (size_t i = 0x18; i < 0x118; i++) { - raw[i] = '.'; - } - std::istringstream stream(raw); - std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); - ASSERT_THAT(header, IsNull()); -} - TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) { - const size_t offset = 0x224; + const size_t offset = kIdmapRawDataOffset; std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset), - idmap_raw_data_len - offset); + kIdmapRawDataLen - offset); std::istringstream stream(raw); std::unique_ptr<const IdmapData::Header> header = IdmapData::Header::FromBinaryStream(stream); @@ -100,9 +89,9 @@ TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) { } TEST(IdmapTests, CreateIdmapDataFromBinaryStream) { - const size_t offset = 0x224; + const size_t offset = kIdmapRawDataOffset; std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset), - idmap_raw_data_len - offset); + kIdmapRawDataLen - offset); std::istringstream stream(raw); std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream); @@ -127,7 +116,7 @@ TEST(IdmapTests, CreateIdmapDataFromBinaryStream) { } TEST(IdmapTests, CreateIdmapFromBinaryStream) { - std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen); std::istringstream stream(raw); auto result = Idmap::FromBinaryStream(stream); @@ -136,13 +125,14 @@ TEST(IdmapTests, CreateIdmapFromBinaryStream) { ASSERT_THAT(idmap->GetHeader(), NotNull()); ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U); - ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x05U); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x07U); ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U); ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U); - ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), 0x11); + ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), kIdmapRawDataPolicies); ASSERT_EQ(idmap->GetHeader()->GetEnforceOverlayable(), true); - ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "targetX.apk"); - ASSERT_EQ(idmap->GetHeader()->GetOverlayPath().to_string(), "overlayX.apk"); + ASSERT_EQ(idmap->GetHeader()->GetTargetPath(), kIdmapRawTargetPath); + ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), kIdmapRawOverlayPath); + ASSERT_EQ(idmap->GetHeader()->GetOverlayName(), kIdmapRawOverlayName); const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); ASSERT_EQ(dataBlocks.size(), 1U); @@ -187,48 +177,23 @@ TEST(IdmapTests, CreateIdmapHeaderFromApkAssets) { std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); ASSERT_THAT(overlay_apk, NotNull()); - auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, - /* enforce_overlayable */ true); + auto idmap_result = Idmap::FromApkAssets( + *target_apk, *overlay_apk, TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage(); auto& idmap = *idmap_result; ASSERT_THAT(idmap, NotNull()); ASSERT_THAT(idmap->GetHeader(), NotNull()); ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U); - ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x05U); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x07U); ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), android::idmap2::TestConstants::TARGET_CRC); ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), android::idmap2::TestConstants::OVERLAY_CRC); ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), PolicyFlags::PUBLIC); ASSERT_EQ(idmap->GetHeader()->GetEnforceOverlayable(), true); - ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), target_apk_path); + ASSERT_EQ(idmap->GetHeader()->GetTargetPath(), target_apk_path); ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path); -} - -Result<std::unique_ptr<const IdmapData>> TestIdmapDataFromApkAssets( - const android::StringPiece& local_target_apk_path, - const android::StringPiece& local_overlay_apk_path, const OverlayManifestInfo& overlay_info, - const PolicyBitmask& fulfilled_policies, bool enforce_overlayable) { - const std::string target_apk_path(GetTestDataPath() + local_target_apk_path.data()); - std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); - if (!target_apk) { - return Error(R"(Failed to load target apk "%s")", target_apk_path.data()); - } - - const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path.data()); - std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); - if (!overlay_apk) { - return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data()); - } - - LogInfo log_info; - auto mapping = ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, overlay_info, - fulfilled_policies, enforce_overlayable, log_info); - - if (!mapping) { - return mapping.GetError(); - } - - return IdmapData::FromResourceMapping(*mapping); + ASSERT_EQ(idmap->GetHeader()->GetOverlayName(), TestConstants::OVERLAY_NAME_ALL_POLICIES); } TEST(IdmapTests, CreateIdmapDataFromApkAssets) { @@ -241,7 +206,8 @@ TEST(IdmapTests, CreateIdmapDataFromApkAssets) { std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); ASSERT_THAT(overlay_apk, NotNull()); - auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, + auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, + TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC, /* enforce_overlayable */ true); ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage(); auto& idmap = *idmap_result; @@ -271,6 +237,29 @@ TEST(IdmapTests, CreateIdmapDataFromApkAssets) { ASSERT_OVERLAY_ENTRY(overlay_entries[3], R::overlay::string::str4, R::target::string::str4); } +TEST(IdmapTests, FailCreateIdmapInvalidName) { + std::string target_apk_path = GetTestDataPath() + "/target/target.apk"; + std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk"; + + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + { + auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, "", PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); + ASSERT_FALSE(idmap_result); + } + { + auto idmap_result = + Idmap::FromApkAssets(*target_apk, *overlay_apk, "unknown", PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); + ASSERT_FALSE(idmap_result); + } +} + TEST(IdmapTests, CreateIdmapDataFromApkAssetsSharedLibOverlay) { std::string target_apk_path = GetTestDataPath() + "/target/target.apk"; std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-shared.apk"; @@ -281,7 +270,8 @@ TEST(IdmapTests, CreateIdmapDataFromApkAssetsSharedLibOverlay) { std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); ASSERT_THAT(overlay_apk, NotNull()); - auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, + auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, + TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC, /* enforce_overlayable */ true); ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage(); auto& idmap = *idmap_result; @@ -296,34 +286,67 @@ TEST(IdmapTests, CreateIdmapDataFromApkAssetsSharedLibOverlay) { const auto& target_entries = data->GetTargetEntries(); ASSERT_EQ(target_entries.size(), 4U); ASSERT_TARGET_ENTRY(target_entries[0], R::target::integer::int1, - R::overlay_shared::integer::int1); - ASSERT_TARGET_ENTRY(target_entries[1], R::target::string::str1, R::overlay_shared::string::str1); - ASSERT_TARGET_ENTRY(target_entries[2], R::target::string::str3, R::overlay_shared::string::str3); - ASSERT_TARGET_ENTRY(target_entries[3], R::target::string::str4, R::overlay_shared::string::str4); + fix_package_id(R::overlay::integer::int1, 0)); + ASSERT_TARGET_ENTRY(target_entries[1], R::target::string::str1, + fix_package_id(R::overlay::string::str1, 0)); + ASSERT_TARGET_ENTRY(target_entries[2], R::target::string::str3, + fix_package_id(R::overlay::string::str3, 0)); + ASSERT_TARGET_ENTRY(target_entries[3], R::target::string::str4, + fix_package_id(R::overlay::string::str4, 0)); const auto& target_inline_entries = data->GetTargetInlineEntries(); ASSERT_EQ(target_inline_entries.size(), 0U); const auto& overlay_entries = data->GetOverlayEntries(); ASSERT_EQ(target_entries.size(), 4U); - ASSERT_OVERLAY_ENTRY(overlay_entries[0], R::overlay_shared::integer::int1, + ASSERT_OVERLAY_ENTRY(overlay_entries[0], fix_package_id(R::overlay::integer::int1, 0), R::target::integer::int1); - ASSERT_OVERLAY_ENTRY(overlay_entries[1], R::overlay_shared::string::str1, + ASSERT_OVERLAY_ENTRY(overlay_entries[1], fix_package_id(R::overlay::string::str1, 0), R::target::string::str1); - ASSERT_OVERLAY_ENTRY(overlay_entries[2], R::overlay_shared::string::str3, + ASSERT_OVERLAY_ENTRY(overlay_entries[2], fix_package_id(R::overlay::string::str3, 0), R::target::string::str3); - ASSERT_OVERLAY_ENTRY(overlay_entries[3], R::overlay_shared::string::str4, + ASSERT_OVERLAY_ENTRY(overlay_entries[3], fix_package_id(R::overlay::string::str4, 0), R::target::string::str4); } +Result<std::unique_ptr<const IdmapData>> TestIdmapDataFromApkAssets( + const std::string& local_target_apk_path, const std::string& local_overlay_apk_path, + const std::string& overlay_name, const PolicyBitmask& fulfilled_policies, + bool enforce_overlayable) { + auto overlay_info = + utils::ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path, overlay_name); + if (!overlay_info) { + return overlay_info.GetError(); + } + + const std::string target_apk_path(GetTestDataPath() + local_target_apk_path); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + if (!target_apk) { + return Error(R"(Failed to load target apk "%s")", target_apk_path.data()); + } + + const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + if (!overlay_apk) { + return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data()); + } + + LogInfo log_info; + auto mapping = ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, *overlay_info, + fulfilled_policies, enforce_overlayable, log_info); + if (!mapping) { + return mapping.GetError(); + } + + return IdmapData::FromResourceMapping(*mapping); +} + TEST(IdmapTests, CreateIdmapDataDoNotRewriteNonOverlayResourceId) { - OverlayManifestInfo info{}; - info.target_package = "test.target"; - info.target_name = "TestResources"; - info.resource_mapping = 0x7f030001; // xml/overlays_different_packages - auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", info, - PolicyFlags::PUBLIC, - /* enforce_overlayable */ false); + auto idmap_data = + TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", "DifferentPackages", + + PolicyFlags::PUBLIC, + /* enforce_overlayable */ false); ASSERT_TRUE(idmap_data) << idmap_data.GetErrorMessage(); auto& data = *idmap_data; @@ -343,12 +366,8 @@ TEST(IdmapTests, CreateIdmapDataDoNotRewriteNonOverlayResourceId) { } TEST(IdmapTests, CreateIdmapDataInlineResources) { - OverlayManifestInfo info{}; - info.target_package = "test.target"; - info.target_name = "TestResources"; - info.resource_mapping = 0x7f030002; // xml/overlays_inline - auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", info, - PolicyFlags::PUBLIC, + auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", + "Inline", PolicyFlags::PUBLIC, /* enforce_overlayable */ false); ASSERT_TRUE(idmap_data) << idmap_data.GetErrorMessage(); @@ -357,7 +376,7 @@ TEST(IdmapTests, CreateIdmapDataInlineResources) { const auto& target_entries = data->GetTargetEntries(); ASSERT_EQ(target_entries.size(), 0U); - constexpr size_t overlay_string_pool_size = 8U; + constexpr size_t overlay_string_pool_size = 10U; const auto& target_inline_entries = data->GetTargetInlineEntries(); ASSERT_EQ(target_inline_entries.size(), 2U); ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1, @@ -370,38 +389,20 @@ TEST(IdmapTests, CreateIdmapDataInlineResources) { ASSERT_EQ(overlay_entries.size(), 0U); } -TEST(IdmapTests, FailToCreateIdmapFromApkAssetsIfPathTooLong) { - std::string target_apk_path(GetTestDataPath()); - for (int i = 0; i < 32; i++) { - target_apk_path += "/target/../"; - } - target_apk_path += "/target/target.apk"; - ASSERT_GT(target_apk_path.size(), kIdmapStringLength); - std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); - ASSERT_THAT(target_apk, NotNull()); - - const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); - std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); - ASSERT_THAT(overlay_apk, NotNull()); - - const auto result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, - /* enforce_overlayable */ true); - ASSERT_FALSE(result); -} - TEST(IdmapTests, IdmapHeaderIsUpToDate) { fclose(stderr); // silence expected warnings from libandroidfw - const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); - std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); - ASSERT_THAT(target_apk, NotNull()); + const std::string target_apk_path = kIdmapRawTargetPath; + const std::string overlay_apk_path = kIdmapRawOverlayPath; + const std::string overlay_name = kIdmapRawOverlayName; + const PolicyBitmask policies = kIdmapRawDataPolicies; + const uint32_t target_crc = kIdmapRawDataTargetCrc; + const uint32_t overlay_crc = kIdmapRawOverlayCrc; - const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); - std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); - ASSERT_THAT(overlay_apk, NotNull()); + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen); + std::istringstream raw_stream(raw); - auto result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, - /* enforce_overlayable */ true); + auto result = Idmap::FromBinaryStream(raw_stream); ASSERT_TRUE(result); const auto idmap = std::move(*result); @@ -411,8 +412,9 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); ASSERT_THAT(header, NotNull()); - ASSERT_TRUE(header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), - PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + ASSERT_TRUE(header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name, + kIdmapRawDataTargetCrc, overlay_crc, policies, + /* enforce_overlayable */ true)); // magic: bytes (0x0, 0x03) std::string bad_magic_string(stream.str()); @@ -425,8 +427,9 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_magic_stream); ASSERT_THAT(bad_magic_header, NotNull()); ASSERT_NE(header->GetMagic(), bad_magic_header->GetMagic()); - ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), - PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name, + target_crc, overlay_crc, policies, + /* enforce_overlayable */ true)); // version: bytes (0x4, 0x07) std::string bad_version_string(stream.str()); @@ -439,8 +442,9 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_version_stream); ASSERT_THAT(bad_version_header, NotNull()); ASSERT_NE(header->GetVersion(), bad_version_header->GetVersion()); - ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), - PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name, + target_crc, overlay_crc, policies, + /* enforce_overlayable */ true)); // target crc: bytes (0x8, 0xb) std::string bad_target_crc_string(stream.str()); @@ -453,8 +457,9 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_target_crc_stream); ASSERT_THAT(bad_target_crc_header, NotNull()); ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc()); - ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), - PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name, + target_crc, overlay_crc, policies, + /* enforce_overlayable */ true)); // overlay crc: bytes (0xc, 0xf) std::string bad_overlay_crc_string(stream.str()); @@ -467,8 +472,9 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_overlay_crc_stream); ASSERT_THAT(bad_overlay_crc_header, NotNull()); ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc()); - ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), - PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name, + target_crc, overlay_crc, policies, + /* enforce_overlayable */ true)); // fulfilled policy: bytes (0x10, 0x13) std::string bad_policy_string(stream.str()); @@ -481,8 +487,9 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_policy_stream); ASSERT_THAT(bad_policy_header, NotNull()); ASSERT_NE(header->GetFulfilledPolicies(), bad_policy_header->GetFulfilledPolicies()); - ASSERT_FALSE(bad_policy_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), - PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + ASSERT_FALSE(bad_policy_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name, + target_crc, overlay_crc, policies, + /* enforce_overlayable */ true)); // enforce overlayable: bytes (0x14) std::string bad_enforce_string(stream.str()); @@ -492,30 +499,47 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_enforce_stream); ASSERT_THAT(bad_enforce_header, NotNull()); ASSERT_NE(header->GetEnforceOverlayable(), bad_enforce_header->GetEnforceOverlayable()); - ASSERT_FALSE(bad_enforce_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), - PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + ASSERT_FALSE(bad_enforce_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name, + target_crc, overlay_crc, policies, + /* enforce_overlayable */ true)); - // target path: bytes (0x18, 0x117) + // target path: bytes (0x1c, 0x27) std::string bad_target_path_string(stream.str()); - bad_target_path_string[0x18] = '\0'; + bad_target_path_string[0x1c] = '\0'; std::stringstream bad_target_path_stream(bad_target_path_string); std::unique_ptr<const IdmapHeader> bad_target_path_header = IdmapHeader::FromBinaryStream(bad_target_path_stream); ASSERT_THAT(bad_target_path_header, NotNull()); ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath()); - ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), - PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name, + target_crc, overlay_crc, policies, + /* enforce_overlayable */ true)); - // overlay path: bytes (0x118, 0x217) + // overlay path: bytes (0x2c, 0x37) std::string bad_overlay_path_string(stream.str()); - bad_overlay_path_string[0x118] = '\0'; + bad_overlay_path_string[0x33] = '\0'; std::stringstream bad_overlay_path_stream(bad_overlay_path_string); std::unique_ptr<const IdmapHeader> bad_overlay_path_header = IdmapHeader::FromBinaryStream(bad_overlay_path_stream); ASSERT_THAT(bad_overlay_path_header, NotNull()); ASSERT_NE(header->GetOverlayPath(), bad_overlay_path_header->GetOverlayPath()); - ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), - PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + ASSERT_FALSE(bad_overlay_path_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name, + target_crc, overlay_crc, policies, + /* enforce_overlayable */ true)); + + // overlay path: bytes (0x3c, 0x47) + std::string bad_overlay_name_string(stream.str()); + bad_overlay_name_string[0x3c] = '\0'; + std::stringstream bad_overlay_name_stream(bad_overlay_name_string); + std::unique_ptr<const IdmapHeader> bad_overlay_name_header = + IdmapHeader::FromBinaryStream(bad_overlay_name_stream); + ASSERT_THAT(bad_overlay_name_header, NotNull()); + ASSERT_NE(header->GetOverlayName(), bad_overlay_name_header->GetOverlayName()); + ASSERT_FALSE(bad_overlay_name_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name, + target_crc, overlay_crc, policies, + /* enforce_overlayable */ true)); + + // overlay name: bytes (0x2c, 0x37) } class TestVisitor : public Visitor { @@ -544,7 +568,7 @@ class TestVisitor : public Visitor { }; TEST(IdmapTests, TestVisitor) { - std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen); std::istringstream stream(raw); const auto idmap = Idmap::FromBinaryStream(stream); diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp index d30fbfcb1d3c..87ce0f13d19e 100644 --- a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp +++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp @@ -15,22 +15,21 @@ */ #include <memory> -#include <sstream> #include <string> #include "R.h" +#include "TestConstants.h" #include "TestHelpers.h" #include "androidfw/ApkAssets.h" -#include "androidfw/Idmap.h" #include "androidfw/ResourceTypes.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "idmap2/Idmap.h" #include "idmap2/PrettyPrintVisitor.h" -using ::testing::NotNull; - using android::ApkAssets; +using android::base::StringPrintf; +using ::testing::NotNull; using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; @@ -46,7 +45,8 @@ TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) { std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); ASSERT_THAT(overlay_apk, NotNull()); - const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, + const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, + TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC, /* enforce_overlayable */ true); ASSERT_TRUE(idmap); @@ -56,15 +56,15 @@ TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) { ASSERT_NE(stream.str().find("target apk path : "), std::string::npos); ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos); - ASSERT_NE(stream.str().find(R::target::integer::literal::int1 + - " -> 0x7f010000 (integer/int1 -> integer/int1)\n"), + ASSERT_NE(stream.str().find(StringPrintf("0x%08x -> 0x%08x (integer/int1 -> integer/int1)\n", + R::target::integer::int1, R::overlay::integer::int1)), std::string::npos); } TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) { fclose(stderr); // silence expected warnings from libandroidfw - std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen); std::istringstream raw_stream(raw); const auto idmap = Idmap::FromBinaryStream(raw_stream); diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h index 854b57fb22aa..ac9b0580d2d9 100644 --- a/cmds/idmap2/tests/R.h +++ b/cmds/idmap2/tests/R.h @@ -23,22 +23,11 @@ namespace android::idmap2 { -static std::string hexify(ResourceId id) { - std::stringstream stream; - stream << std::hex << static_cast<uint32_t>(id); - return stream.str(); -} - // clang-format off namespace R::target { namespace integer { // NOLINT(runtime/indentation_namespace) constexpr ResourceId int1 = 0x7f010000; - - namespace literal { // NOLINT(runtime/indentation_namespace) - inline const std::string int1 = hexify(R::target::integer::int1); - } } - namespace string { // NOLINT(runtime/indentation_namespace) constexpr ResourceId not_overlayable = 0x7f020003; constexpr ResourceId other = 0x7f020004; @@ -54,56 +43,31 @@ namespace R::target { constexpr ResourceId str1 = 0x7f02000e; constexpr ResourceId str3 = 0x7f020010; constexpr ResourceId str4 = 0x7f020011; - - namespace literal { // NOLINT(runtime/indentation_namespace) - inline const std::string str1 = hexify(R::target::string::str1); - inline const std::string str3 = hexify(R::target::string::str3); - inline const std::string str4 = hexify(R::target::string::str4); - } } // namespace string } // namespace R::target namespace R::overlay { namespace integer { // NOLINT(runtime/indentation_namespace) constexpr ResourceId int1 = 0x7f010000; + constexpr ResourceId not_in_target = 0x7f010001; } namespace string { // NOLINT(runtime/indentation_namespace) - constexpr ResourceId str1 = 0x7f020000; - constexpr ResourceId str3 = 0x7f020001; - constexpr ResourceId str4 = 0x7f020002; - } -} - -namespace R::overlay_shared { - namespace integer { // NOLINT(runtime/indentation_namespace) - constexpr ResourceId int1 = 0x00010000; - } - namespace string { // NOLINT(runtime/indentation_namespace) - constexpr ResourceId str1 = 0x00020000; - constexpr ResourceId str3 = 0x00020001; - constexpr ResourceId str4 = 0x00020002; + constexpr ResourceId not_overlayable = 0x7f020000; + constexpr ResourceId other = 0x7f020001; + constexpr ResourceId policy_actor = 0x7f020002; + constexpr ResourceId policy_config_signature = 0x7f020003; + constexpr ResourceId policy_odm = 0x7f020004; + constexpr ResourceId policy_oem = 0x7f020005; + constexpr ResourceId policy_product = 0x7f020006; + constexpr ResourceId policy_public = 0x7f020007; + constexpr ResourceId policy_signature = 0x7f020008; + constexpr ResourceId policy_system = 0x7f020009; + constexpr ResourceId policy_system_vendor = 0x7f02000a; + constexpr ResourceId str1 = 0x7f02000b; + constexpr ResourceId str3 = 0x7f02000c; + constexpr ResourceId str4 = 0x7f02000d; } } - -namespace R::system_overlay::string { - constexpr ResourceId policy_public = 0x7f010000; - constexpr ResourceId policy_system = 0x7f010001; - constexpr ResourceId policy_system_vendor = 0x7f010002; -} - -namespace R::system_overlay_invalid::string { - constexpr ResourceId not_overlayable = 0x7f010000; - constexpr ResourceId other = 0x7f010001; - constexpr ResourceId policy_actor = 0x7f010002; - constexpr ResourceId policy_config_signature = 0x7f010003; - constexpr ResourceId policy_odm = 0x7f010004; - constexpr ResourceId policy_oem = 0x7f010005; - constexpr ResourceId policy_product = 0x7f010006; - constexpr ResourceId policy_public = 0x7f010007; - constexpr ResourceId policy_signature = 0x7f010008; - constexpr ResourceId policy_system = 0x7f010009; - constexpr ResourceId policy_system_vendor = 0x7f01000a; -} // namespace R::system_overlay_invalid::string // clang-format on } // namespace android::idmap2 diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp index 95bd94733ab3..88f85efb0f84 100644 --- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp +++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp @@ -56,8 +56,9 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); ASSERT_THAT(overlay_apk, NotNull()); - const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, - /* enforce_overlayable */ true); + const auto idmap = + Idmap::FromApkAssets(*target_apk, *overlay_apk, TestConstants::OVERLAY_NAME_DEFAULT, + PolicyFlags::PUBLIC, /* enforce_overlayable */ true); ASSERT_TRUE(idmap); std::stringstream stream; @@ -65,7 +66,7 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { (*idmap)->accept(&visitor); ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "00000005 version\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000007 version\n", stream.str()); ASSERT_CONTAINS_REGEX( StringPrintf(ADDRESS "%s target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING), stream.str()); @@ -76,22 +77,34 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { ASSERT_CONTAINS_REGEX(ADDRESS "00000001 enforce overlayable\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS " 7f target package id\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS " 7f overlay package id\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "00000004 target entry count\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "00000008 string pool index offset\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 overlay id: integer/int1\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 overlay id: integer/int1\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "000000b4 string pool size\n", stream.str()); - ASSERT_CONTAINS_REGEX("000002bc: ........ string pool: ...\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000004 target entry count", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000000 target inline entry count", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "0000000a string pool index offset", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 overlay id: integer/int1", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e target id: string/str1", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b overlay id: string/str1", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f020010 target id: string/str3", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c overlay id: string/str3", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f020011 target id: string/str4", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d overlay id: string/str4", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 overlay id: integer/int1", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b overlay id: string/str1", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e target id: string/str1", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c overlay id: string/str3", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f020010 target id: string/str3", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d overlay id: string/str4", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f020011 target id: string/str4", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "000000b4 string pool size", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "........ string pool", stream.str()); } TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) { fclose(stderr); // silence expected warnings from libandroidfw - std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen); std::istringstream raw_stream(raw); const auto idmap = Idmap::FromBinaryStream(raw_stream); @@ -102,11 +115,17 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) { (*idmap)->accept(&visitor); ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "00000005 version\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000007 version\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00001234 target crc\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00005678 overlay crc\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000011 fulfilled policies: public|signature\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000001 enforce overlayable\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "0000000b target path size\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "........ target path: targetX.apk\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "0000000c overlay path size\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "........ overlay path: overlayX.apk\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "0000000b overlay name size\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "........ overlay name: OverlayName\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS " 7f target package id\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS " 7f overlay package id\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000003 target entry count\n", stream.str()); @@ -121,7 +140,7 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) { ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 overlay id\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "7f030002 target id\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000004 string pool size\n", stream.str()); - ASSERT_CONTAINS_REGEX("00000278: ........ string pool: ...\n", stream.str()); + ASSERT_CONTAINS_REGEX("000000a8: ........ string pool\n", stream.str()); } } // namespace android::idmap2 diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp index 185e9292346d..0362529c4f3b 100644 --- a/cmds/idmap2/tests/ResourceMappingTests.cpp +++ b/cmds/idmap2/tests/ResourceMappingTests.cpp @@ -17,12 +17,10 @@ #include <cstdio> // fclose #include <fstream> #include <memory> -#include <sstream> #include <string> -#include <utility> -#include <vector> #include "R.h" +#include "TestConstants.h" #include "TestHelpers.h" #include "androidfw/ResourceTypes.h" #include "gmock/gmock.h" @@ -43,38 +41,32 @@ namespace android::idmap2 { ASSERT_TRUE(result) << result.GetErrorMessage(); \ } while (0) -Result<ResourceMapping> TestGetResourceMapping(const android::StringPiece& local_target_apk_path, - const android::StringPiece& local_overlay_apk_path, - const OverlayManifestInfo& overlay_info, +Result<ResourceMapping> TestGetResourceMapping(const std::string& local_target_apk_path, + const std::string& local_overlay_apk_path, + const std::string& overlay_name, const PolicyBitmask& fulfilled_policies, bool enforce_overlayable) { - const std::string target_apk_path(GetTestDataPath() + local_target_apk_path.data()); + auto overlay_info = + ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path, overlay_name); + if (!overlay_info) { + return overlay_info.GetError(); + } + + const std::string target_apk_path(GetTestDataPath() + local_target_apk_path); std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); if (!target_apk) { return Error(R"(Failed to load target apk "%s")", target_apk_path.data()); } - const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path.data()); + const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path); std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); if (!overlay_apk) { return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data()); } LogInfo log_info; - return ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, overlay_info, fulfilled_policies, - enforce_overlayable, log_info); -} - -Result<ResourceMapping> TestGetResourceMapping(const android::StringPiece& local_target_apk_path, - const android::StringPiece& local_overlay_apk_path, - const PolicyBitmask& fulfilled_policies, - bool enforce_overlayable) { - auto overlay_info = ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path.data()); - if (!overlay_info) { - return overlay_info.GetError(); - } - return TestGetResourceMapping(local_target_apk_path, local_overlay_apk_path, *overlay_info, - fulfilled_policies, enforce_overlayable); + return ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, *overlay_info, + fulfilled_policies, enforce_overlayable, log_info); } Result<Unit> MappingExists(const ResourceMapping& mapping, ResourceId target_resource, @@ -136,13 +128,8 @@ Result<Unit> MappingExists(const ResourceMapping& mapping, const ResourceId& tar } TEST(ResourceMappingTests, ResourcesFromApkAssetsLegacy) { - OverlayManifestInfo info{}; - info.target_package = "test.target"; - info.target_name = "TestResources"; - info.resource_mapping = 0U; // no xml - auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info, - PolicyFlags::PUBLIC, - /* enforce_overlayable */ false); + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-legacy.apk", "", + PolicyFlags::PUBLIC, /* enforce_overlayable */ false); ASSERT_TRUE(resources) << resources.GetErrorMessage(); auto& res = *resources; @@ -158,11 +145,7 @@ TEST(ResourceMappingTests, ResourcesFromApkAssetsLegacy) { } TEST(ResourceMappingTests, ResourcesFromApkAssetsNonMatchingNames) { - OverlayManifestInfo info{}; - info.target_package = "test.target"; - info.target_name = "TestResources"; - info.resource_mapping = 0x7f030003; // xml/overlays_swap - auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info, + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", "SwapNames", PolicyFlags::PUBLIC, /* enforce_overlayable */ false); @@ -178,12 +161,8 @@ TEST(ResourceMappingTests, ResourcesFromApkAssetsNonMatchingNames) { } TEST(ResourceMappingTests, DoNotRewriteNonOverlayResourceId) { - OverlayManifestInfo info{}; - info.target_package = "test.target"; - info.target_name = "TestResources"; - info.resource_mapping = 0x7f030001; // xml/overlays_different_packages - auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info, - PolicyFlags::PUBLIC, + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", + "DifferentPackages", PolicyFlags::PUBLIC, /* enforce_overlayable */ false); ASSERT_TRUE(resources) << resources.GetErrorMessage(); @@ -192,19 +171,15 @@ TEST(ResourceMappingTests, DoNotRewriteNonOverlayResourceId) { ASSERT_EQ(res.GetOverlayToTargetMap().size(), 1U); ASSERT_RESULT(MappingExists(res, R::target::string::str1, 0x0104000a, false /* rewrite */)); // -> android:string/ok - ASSERT_RESULT(MappingExists(res, R::target::string::str3, 0x7f020001, true /* rewrite */)); + ASSERT_RESULT( + MappingExists(res, R::target::string::str3, R::overlay::string::str3, true /* rewrite */)); } TEST(ResourceMappingTests, InlineResources) { - OverlayManifestInfo info{}; - info.target_package = "test.target"; - info.target_name = "TestResources"; - info.resource_mapping = 0x7f030002; // xml/overlays_inline - auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info, - PolicyFlags::PUBLIC, - /* enforce_overlayable */ false); + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", "Inline", + PolicyFlags::PUBLIC, /* enforce_overlayable */ false); - constexpr size_t overlay_string_pool_size = 8U; + constexpr size_t overlay_string_pool_size = 10U; ASSERT_TRUE(resources) << resources.GetErrorMessage(); auto& res = *resources; ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U); @@ -215,28 +190,27 @@ TEST(ResourceMappingTests, InlineResources) { } TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublic) { - auto resources = - TestGetResourceMapping("/target/target.apk", "/system-overlay/system-overlay.apk", - PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC, - /* enforce_overlayable */ true); + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", + TestConstants::OVERLAY_NAME_ALL_POLICIES, + PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); ASSERT_TRUE(resources) << resources.GetErrorMessage(); auto& res = *resources; ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U); ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, - R::system_overlay::string::policy_public, false /* rewrite */)); + R::overlay::string::policy_public, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, - R::system_overlay::string::policy_system, false /* rewrite */)); + R::overlay::string::policy_system, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_system_vendor, - R::system_overlay::string::policy_system_vendor, - false /* rewrite */)); + R::overlay::string::policy_system_vendor, true /* rewrite */)); } // Resources that are not declared as overlayable and resources that a protected by policies the // overlay does not fulfill must not map to overlay resources. TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) { - auto resources = TestGetResourceMapping("/target/target.apk", - "/system-overlay-invalid/system-overlay-invalid.apk", + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", + TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC, /* enforce_overlayable */ true); @@ -244,22 +218,19 @@ TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) { auto& res = *resources; ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U); ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, - R::system_overlay_invalid::string::policy_public, - false /* rewrite */)); + R::overlay::string::policy_public, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, - R::system_overlay_invalid::string::policy_system, - false /* rewrite */)); + R::overlay::string::policy_system, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_system_vendor, - R::system_overlay_invalid::string::policy_system_vendor, - false /* rewrite */)); + R::overlay::string::policy_system_vendor, true /* rewrite */)); } // Resources that are not declared as overlayable and resources that a protected by policies the // overlay does not fulfilled can map to overlay resources when overlayable enforcement is turned // off. TEST(ResourceMappingTests, ResourcesFromApkAssetsPolicySystemPublicInvalidIgnoreOverlayable) { - auto resources = TestGetResourceMapping("/target/target.apk", - "/system-overlay-invalid/system-overlay-invalid.apk", + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", + TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC, /* enforce_overlayable */ false); @@ -267,41 +238,33 @@ TEST(ResourceMappingTests, ResourcesFromApkAssetsPolicySystemPublicInvalidIgnore auto& res = *resources; ASSERT_EQ(res.GetTargetToOverlayMap().size(), 11U); ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable, - R::system_overlay_invalid::string::not_overlayable, - false /* rewrite */)); - ASSERT_RESULT(MappingExists(res, R::target::string::other, - R::system_overlay_invalid::string::other, false /* rewrite */)); + R::overlay::string::not_overlayable, true /* rewrite */)); + ASSERT_RESULT( + MappingExists(res, R::target::string::other, R::overlay::string::other, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_actor, - R::system_overlay_invalid::string::policy_actor, - false /* rewrite */)); - ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, - R::system_overlay_invalid::string::policy_odm, false /* rewrite */)); - ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, - R::system_overlay_invalid::string::policy_oem, false /* rewrite */)); + R::overlay::string::policy_actor, true /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, R::overlay::string::policy_odm, + true /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, R::overlay::string::policy_oem, + true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_product, - R::system_overlay_invalid::string::policy_product, - false /* rewrite */)); + R::overlay::string::policy_product, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, - R::system_overlay_invalid::string::policy_public, - false /* rewrite */)); + R::overlay::string::policy_public, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_config_signature, - R::system_overlay_invalid::string::policy_config_signature, - false /* rewrite */)); + R::overlay::string::policy_config_signature, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature, - R::system_overlay_invalid::string::policy_signature, - false /* rewrite */)); + R::overlay::string::policy_signature, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, - R::system_overlay_invalid::string::policy_system, - false /* rewrite */)); + R::overlay::string::policy_system, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_system_vendor, - R::system_overlay_invalid::string::policy_system_vendor, - false /* rewrite */)); + R::overlay::string::policy_system_vendor, true /* rewrite */)); } -// Overlays that do not target an <overlayable> tag can overlay resources defined within any -// <overlayable> tag. +// Overlays that do not target an <overlayable> tag can overlay any resource if overlayable +// enforcement is disabled. TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTargetName) { - auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-no-name.apk", + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-legacy.apk", "", PolicyFlags::PUBLIC, /* enforce_overlayable */ false); @@ -321,9 +284,10 @@ TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTarget // Overlays that are neither pre-installed nor signed with the same signature as the target cannot // overlay packages that have not defined overlayable resources. TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) { - auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk", - "/overlay/overlay-no-name.apk", PolicyFlags::PUBLIC, - /* enforce_overlayable */ true); + auto resources = + TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay.apk", + "NoTargetName", PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); ASSERT_TRUE(resources) << resources.GetErrorMessage(); ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U); @@ -334,46 +298,36 @@ TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) { // defined overlayable resources. TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) { auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void { - auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk", - "/system-overlay-invalid/system-overlay-invalid.apk", - fulfilled_policies, - /* enforce_overlayable */ true); + auto resources = + TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay.apk", + TestConstants::OVERLAY_NAME_ALL_POLICIES, fulfilled_policies, + /* enforce_overlayable */ true); ASSERT_TRUE(resources) << resources.GetErrorMessage(); auto& res = *resources; ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 11U); ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable, - R::system_overlay_invalid::string::not_overlayable, - false /* rewrite */)); - ASSERT_RESULT(MappingExists(res, R::target::string::other, - R::system_overlay_invalid::string::other, false /* rewrite */)); + R::overlay::string::not_overlayable, true /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::other, R::overlay::string::other, + true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_actor, - R::system_overlay_invalid::string::policy_actor, - false /* rewrite */)); - ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, - R::system_overlay_invalid::string::policy_odm, - false /* rewrite */)); - ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, - R::system_overlay_invalid::string::policy_oem, - false /* rewrite */)); + R::overlay::string::policy_actor, true /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, R::overlay::string::policy_odm, + true /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, R::overlay::string::policy_oem, + true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_product, - R::system_overlay_invalid::string::policy_product, - false /* rewrite */)); + R::overlay::string::policy_product, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, - R::system_overlay_invalid::string::policy_public, - false /* rewrite */)); + R::overlay::string::policy_public, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_config_signature, - R::system_overlay_invalid::string::policy_config_signature, - false /* rewrite */)); + R::overlay::string::policy_config_signature, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature, - R::system_overlay_invalid::string::policy_signature, - false /* rewrite */)); + R::overlay::string::policy_signature, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, - R::system_overlay_invalid::string::policy_system, - false /* rewrite */)); + R::overlay::string::policy_system, true /* rewrite */)); ASSERT_RESULT(MappingExists(res, R::target::string::policy_system_vendor, - R::system_overlay_invalid::string::policy_system_vendor, - false /* rewrite */)); + R::overlay::string::policy_system_vendor, true /* rewrite */)); }; CheckEntries(PolicyFlags::SIGNATURE); diff --git a/cmds/idmap2/tests/ResourceUtilsTests.cpp b/cmds/idmap2/tests/ResourceUtilsTests.cpp index 9ed807ccd8f9..1f6bf49f5f0e 100644 --- a/cmds/idmap2/tests/ResourceUtilsTests.cpp +++ b/cmds/idmap2/tests/ResourceUtilsTests.cpp @@ -59,4 +59,26 @@ TEST_F(ResourceUtilsTests, ResToTypeEntryNameNoSuchResourceId) { ASSERT_FALSE(name); } -} // namespace android::idmap2 +TEST_F(ResourceUtilsTests, InvalidValidOverlayNameInvalidAttributes) { + auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk", + "InvalidName"); + ASSERT_FALSE(info); +} + +TEST_F(ResourceUtilsTests, ValidOverlayNameInvalidAttributes) { + auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk", + "ValidName"); + ASSERT_FALSE(info); +} + +TEST_F(ResourceUtilsTests, ValidOverlayNameAndTargetPackageInvalidAttributes) { + auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk", + "ValidNameAndTargetPackage"); + ASSERT_TRUE(info); + ASSERT_EQ("ValidNameAndTargetPackage", info->name); + ASSERT_EQ("Valid", info->target_package); + ASSERT_EQ("", info->target_name); // Attribute resource id could not be found + ASSERT_EQ(0, info->resource_mapping); // Attribute resource id could not be found +} + +}// namespace android::idmap2 diff --git a/cmds/idmap2/tests/TestConstants.h b/cmds/idmap2/tests/TestConstants.h index 69575b8f9c01..d5799adf0ec3 100644 --- a/cmds/idmap2/tests/TestConstants.h +++ b/cmds/idmap2/tests/TestConstants.h @@ -22,8 +22,11 @@ namespace android::idmap2::TestConstants { constexpr const auto TARGET_CRC = 0x7c2d4719; constexpr const auto TARGET_CRC_STRING = "7c2d4719"; -constexpr const auto OVERLAY_CRC = 0x5afff726; -constexpr const auto OVERLAY_CRC_STRING = "5afff726"; +constexpr const auto OVERLAY_CRC = 0xb71095cf; +constexpr const auto OVERLAY_CRC_STRING = "b71095cf"; + +constexpr const char* OVERLAY_NAME_DEFAULT = "Default"; +constexpr const char* OVERLAY_NAME_ALL_POLICIES = "AllPolicies"; } // namespace android::idmap2::TestConstants diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h index d0a8e3db8eca..842af3dd7b3c 100644 --- a/cmds/idmap2/tests/TestHelpers.h +++ b/cmds/idmap2/tests/TestHelpers.h @@ -30,7 +30,7 @@ const unsigned char idmap_raw_data[] = { 0x49, 0x44, 0x4d, 0x50, // 0x4: version - 0x05, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, // 0x8: target crc 0x34, 0x12, 0x00, 0x00, @@ -44,125 +44,114 @@ const unsigned char idmap_raw_data[] = { // 0x14: enforce overlayable 0x01, 0x00, 0x00, 0x00, - // 0x18: target path "targetX.apk" - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - // 0x118: overlay path "overlayX.apk" - 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - // 0x218: debug string + // 0x18: target path length + 0x0b, 0x00, 0x00, 0x00, + + // 0x1c: target path "targetX.apk" + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, + + // 0x28: overlay path length + 0x0c, 0x00, 0x00, 0x00, + + // 0x2c: overlay path "overlayX.apk" + 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x58, 0x2e, 0x61, 0x70, 0x6b, + + // 0x38: overlay name length + 0x0b, 0x00, 0x00, 0x00, + + // 0x3c: overlay name "OverlayName" + 0x4f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6D, 0x65, 0x00, + + // 0x48 -> 4c: debug string // string length, 0x05, 0x00, 0x00, 0x00, - // 0x21c string contents "debug\0\0\0" (padded to word alignment) + // 0x4c string contents "debug\0\0\0" (padded to word alignment) 0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00, // DATA HEADER - // 0x224: target_package_id + // 0x54: target_package_id 0x7f, - // 0x225: overlay_package_id + // 0x55: overlay_package_id 0x7f, - // 0x226: padding + // 0x56: padding 0x00, 0x00, - // 0x228: target_entry_count + // 0x58: target_entry_count 0x03, 0x00, 0x00, 0x00, - // 0x22c: target_inline_entry_count + // 0x5c: target_inline_entry_count 0x01, 0x00, 0x00, 0x00, - // 0x230: overlay_entry_count + // 0x60: overlay_entry_count 0x03, 0x00, 0x00, 0x00, - // 0x234: string_pool_offset + // 0x64: string_pool_offset 0x00, 0x00, 0x00, 0x00, // TARGET ENTRIES - // 0x238: target id (0x7f020000) + // 0x68: target id (0x7f020000) 0x00, 0x00, 0x02, 0x7f, - // 0x23c: overlay_id (0x7f020000) + // 0x6c: overlay_id (0x7f020000) 0x00, 0x00, 0x02, 0x7f, - // 0x240: target id (0x7f030000) + // 0x70: target id (0x7f030000) 0x00, 0x00, 0x03, 0x7f, - // 0x244: overlay_id (0x7f030000) + // 0x74: overlay_id (0x7f030000) 0x00, 0x00, 0x03, 0x7f, - // 0x248: target id (0x7f030002) + // 0x78: target id (0x7f030002) 0x02, 0x00, 0x03, 0x7f, - // 0x24c: overlay_id (0x7f030001) + // 0x7c: overlay_id (0x7f030001) 0x01, 0x00, 0x03, 0x7f, // INLINE TARGET ENTRIES - // 0x250: target_id + // 0x80: target_id 0x00, 0x00, 0x04, 0x7f, - // 0x254: Res_value::size (value ignored by idmap) + // 0x84: Res_value::size (value ignored by idmap) 0x08, 0x00, - // 0x256: Res_value::res0 (value ignored by idmap) + // 0x87: Res_value::res0 (value ignored by idmap) 0x00, - // 0x257: Res_value::dataType (TYPE_INT_HEX) + // 0x88: Res_value::dataType (TYPE_INT_HEX) 0x11, - // 0x258: Res_value::data + // 0x8c: Res_value::data 0x78, 0x56, 0x34, 0x12, // OVERLAY ENTRIES - // 0x25c: 0x7f020000 -> 0x7f020000 + // 0x90: 0x7f020000 -> 0x7f020000 0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f, - // 0x264: 0x7f030000 -> 0x7f030000 + // 0x98: 0x7f030000 -> 0x7f030000 0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f, - // 0x26c: 0x7f030001 -> 0x7f030002 + // 0xa0: 0x7f030001 -> 0x7f030002 0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f, - // 0x274: string pool + // 0xa4: string pool // string length, 0x04, 0x00, 0x00, 0x00, - // 0x278 string contents "test" (padded to word alignment) + // 0xa8 string contents "test" 0x74, 0x65, 0x73, 0x74}; -const unsigned int idmap_raw_data_len = 0x27c; +const unsigned int kIdmapRawDataLen = 0xac; +const unsigned int kIdmapRawDataOffset = 0x54; +const unsigned int kIdmapRawDataTargetCrc = 0x1234; +const unsigned int kIdmapRawOverlayCrc = 0x5678; +const unsigned int kIdmapRawDataPolicies = 0x11; +inline const std::string kIdmapRawTargetPath = "targetX.apk"; +inline const std::string kIdmapRawOverlayPath = "overlayX.apk"; +inline const std::string kIdmapRawOverlayName = "OverlayName"; std::string GetTestDataPath(); diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml index cf3691c3b3cf..2c50dee05b11 100644 --- a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml +++ b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml @@ -13,14 +13,37 @@ See the License for the specific language governing permissions and limitations under the License. --> -<manifest - xmlns:android="http://schemas.android.com/apk/res/android" - package="test.overlay"> - +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="test.overlay"> <application android:hasCode="false"/> - <overlay - android:targetPackage="test.target" - android:targetName="TestResources" - android:resourcesMap="@xml/overlays"/> + <overlay android:name="Default" + android:targetPackage="test.target" + android:targetName="TestResources" + android:resourcesMap="@xml/overlays"/> + + <overlay android:name="NoTargetName" + android:targetPackage="test.target" + android:resourcesMap="@xml/overlays"/> + + <overlay android:name="Inline" + android:targetName="TestResources" + android:targetPackage="test.target" + android:resourcesMap="@xml/overlays_inline"/> + + <overlay android:name="DifferentPackages" + android:targetName="TestResources" + android:targetPackage="test.target" + android:resourcesMap="@xml/overlays_different_package"/> + + <overlay android:name="SwapNames" + android:targetName="TestResources" + android:targetPackage="test.target" + android:resourcesMap="@xml/overlays_swap"/> + + <overlay android:name="AllPolicies" + android:targetName="TestResources" + android:targetPackage="test.target" + android:resourcesMap="@xml/overlays_policies"/> + </manifest> diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestInvalid.xml index 1c4dae6cf69c..d61c36cad60c 100644 --- a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml +++ b/cmds/idmap2/tests/data/overlay/AndroidManifestInvalid.xml @@ -13,12 +13,22 @@ See the License for the specific language governing permissions and limitations under the License. --> -<manifest - xmlns:android="http://schemas.android.com/apk/res/android" - package="test.overlay.static1"> - <overlay - android:targetPackage="test.target" - android:targetName="TestResources" - android:isStatic="true" - android:priority="1" /> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="test.overlay"> + <application android:hasCode="false"/> + + <overlay name="InvalidName" + android:targetName="Invalid" + android:targetPackage="Invalid" + android:resourcesMap="@xml/overlays_swap"/> + + <overlay android:name="ValidName" + targetName="Invalid" + targetPackage="Invalid" + resourcesMap="@xml/overlays_swap"/> + + <overlay android:name="ValidNameAndTargetPackage" + targetName="Invalid" + android:targetPackage="Valid" + resourcesMap="@xml/overlays_swap"/> </manifest> diff --git a/cmds/idmap2/tests/data/system-overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestLegacy.xml index 9e6a4536cb51..9fc2105fe827 100644 --- a/cmds/idmap2/tests/data/system-overlay/AndroidManifest.xml +++ b/cmds/idmap2/tests/data/overlay/AndroidManifestLegacy.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2019 The Android Open Source Project +<!-- 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. @@ -13,11 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> -<manifest - xmlns:android="http://schemas.android.com/apk/res/android" - package="test.overlay.system"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="test.overlay"> <application android:hasCode="false"/> - <overlay - android:targetPackage="test.target" - android:targetName="TestResources"/> + + <overlay android:targetPackage="test.target"/> </manifest> diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml deleted file mode 100644 index 70efc8603670..000000000000 --- a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<manifest - xmlns:android="http://schemas.android.com/apk/res/android" - package="test.overlay.static2"> - <overlay - android:targetPackage="test.target" - android:targetName="TestResources" - android:isStatic="true" - android:priority="2" /> -</manifest> diff --git a/cmds/idmap2/tests/data/overlay/build b/cmds/idmap2/tests/data/overlay/build index 114b099598fa..7b1a66f31e61 100755 --- a/cmds/idmap2/tests/data/overlay/build +++ b/cmds/idmap2/tests/data/overlay/build @@ -26,37 +26,23 @@ aapt2 link \ aapt2 link \ --no-resource-removal \ -I "$FRAMEWORK_RES_APK" \ - --manifest AndroidManifestNoName.xml \ - -o overlay-no-name.apk \ - compiled.flata - -aapt2 link \ - --no-resource-removal \ - -I "$FRAMEWORK_RES_APK" \ - --manifest AndroidManifestNoNameStatic.xml \ - -o overlay-no-name-static.apk \ - compiled.flata - -aapt2 link \ - --no-resource-removal \ - -I "$FRAMEWORK_RES_APK" \ - --manifest AndroidManifestStatic1.xml \ - -o overlay-static-1.apk \ + --manifest AndroidManifest.xml \ + -o overlay-shared.apk \ + --shared-lib \ compiled.flata aapt2 link \ --no-resource-removal \ -I "$FRAMEWORK_RES_APK" \ - --manifest AndroidManifestStatic2.xml \ - -o overlay-static-2.apk \ + --manifest AndroidManifestLegacy.xml \ + -o overlay-legacy.apk \ compiled.flata aapt2 link \ --no-resource-removal \ - --shared-lib \ -I "$FRAMEWORK_RES_APK" \ - --manifest AndroidManifest.xml \ - -o overlay-shared.apk \ + --manifest AndroidManifestInvalid.xml \ + -o overlay-invalid.apk \ compiled.flata rm compiled.flata diff --git a/cmds/idmap2/tests/data/overlay/overlay-invalid.apk b/cmds/idmap2/tests/data/overlay/overlay-invalid.apk Binary files differnew file mode 100644 index 000000000000..888c871e4101 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/overlay-invalid.apk diff --git a/cmds/idmap2/tests/data/overlay/overlay-legacy.apk b/cmds/idmap2/tests/data/overlay/overlay-legacy.apk Binary files differnew file mode 100644 index 000000000000..f03eebbf6872 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/overlay-legacy.apk diff --git a/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk b/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk Binary files differdeleted file mode 100644 index dab25b1f8131..000000000000 --- a/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk +++ /dev/null diff --git a/cmds/idmap2/tests/data/overlay/overlay-no-name.apk b/cmds/idmap2/tests/data/overlay/overlay-no-name.apk Binary files differdeleted file mode 100644 index c8b95c2601ad..000000000000 --- a/cmds/idmap2/tests/data/overlay/overlay-no-name.apk +++ /dev/null diff --git a/cmds/idmap2/tests/data/overlay/overlay-shared.apk b/cmds/idmap2/tests/data/overlay/overlay-shared.apk Binary files differindex 0a8b7372172e..3c896ea79505 100644 --- a/cmds/idmap2/tests/data/overlay/overlay-shared.apk +++ b/cmds/idmap2/tests/data/overlay/overlay-shared.apk diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk Binary files differdeleted file mode 100644 index fd41182f8493..000000000000 --- a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk +++ /dev/null diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk Binary files differdeleted file mode 100644 index b24765fc666a..000000000000 --- a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk +++ /dev/null diff --git a/cmds/idmap2/tests/data/overlay/overlay.apk b/cmds/idmap2/tests/data/overlay/overlay.apk Binary files differindex 870575efa10c..c7ea623f64f2 100644 --- a/cmds/idmap2/tests/data/overlay/overlay.apk +++ b/cmds/idmap2/tests/data/overlay/overlay.apk diff --git a/cmds/idmap2/tests/data/overlay/res/values/values.xml b/cmds/idmap2/tests/data/overlay/res/values/values.xml index 815d1a88fa7b..6e98b212cee6 100644 --- a/cmds/idmap2/tests/data/overlay/res/values/values.xml +++ b/cmds/idmap2/tests/data/overlay/res/values/values.xml @@ -18,4 +18,25 @@ <string name="str3">overlay-3</string> <integer name="int1">-1</integer> <integer name="not_in_target">-1</integer> + + <!-- This overlay will fulfill the policies "public|system". This allows it overlay the + following resources. --> + <string name="overlay_policy_system">overlaid</string> + <string name="overlay_policy_system_vendor">overlaid</string> + <string name="overlay_policy_public">overlaid</string> + + <!-- Requests to overlay a resource that belongs to a policy the overlay does not fulfill. --> + <string name="overlay_policy_product">overlaid</string> + <string name="overlay_policy_signature">overlaid</string> + <string name="overlay_policy_odm">overlaid</string> + <string name="overlay_policy_oem">overlaid</string> + <string name="overlay_policy_actor">overlaid</string> + <string name="overlay_policy_config_signature">overlaid</string> + + <!-- Requests to overlay a resource that is not declared as overlayable. --> + <string name="overlay_not_overlayable">overlaid</string> + + <!-- Requests to overlay a resource that is defined in an overlayable with a name other than + the targetName in the manifest. --> + <string name="overlay_other">overlaid</string> </resources> diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays_policies.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays_policies.xml new file mode 100644 index 000000000000..747f448d46fd --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays_policies.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<overlay> + <item target="string/policy_system" value="@string/overlay_policy_system"/> + <item target="string/policy_system_vendor" value="@string/overlay_policy_system_vendor" /> + <item target="string/policy_public" value="@string/overlay_policy_public" /> + <item target="string/policy_product" value="@string/overlay_policy_product"/> + <item target="string/policy_signature" value="@string/overlay_policy_signature" /> + <item target="string/policy_odm" value="@string/overlay_policy_odm" /> + <item target="string/policy_oem" value="@string/overlay_policy_oem"/> + <item target="string/policy_actor" value="@string/overlay_policy_actor" /> + <item target="string/policy_config_signature" value="@string/overlay_policy_config_signature" /> + + <!-- Requests to overlay a resource that is not declared as overlayable. --> + <item target="string/not_overlayable" value="@string/overlay_not_overlayable" /> + + <!-- Requests to overlay a resource that is defined in an overlayable with a name other than + the targetName in the manifest. --> + <item target="string/other" value="@string/overlay_other" /> +</overlay>
\ No newline at end of file diff --git a/cmds/idmap2/tests/data/signature-overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/signature-overlay/AndroidManifest.xml deleted file mode 100644 index 5df0bea555b1..000000000000 --- a/cmds/idmap2/tests/data/signature-overlay/AndroidManifest.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<manifest - xmlns:android="http://schemas.android.com/apk/res/android" - package="test.overlay.system"> - <application android:hasCode="false"/> - <overlay - android:targetPackage="test.target" - android:targetName="TestResources" - android:isStatic="true" - android:priority="10"/> -</manifest> diff --git a/cmds/idmap2/tests/data/signature-overlay/build b/cmds/idmap2/tests/data/signature-overlay/build deleted file mode 100755 index fdd8301c3b7d..000000000000 --- a/cmds/idmap2/tests/data/signature-overlay/build +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (C) 2019 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -FRAMEWORK_RES_APK=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar - -aapt2 compile --dir res -o compiled.flata - -aapt2 link \ - --no-resource-removal \ - -I "$FRAMEWORK_RES_APK" \ - --manifest AndroidManifest.xml \ - -o signature-overlay.apk \ - compiled.flata - -rm compiled.flata diff --git a/cmds/idmap2/tests/data/signature-overlay/res/values/values.xml b/cmds/idmap2/tests/data/signature-overlay/res/values/values.xml deleted file mode 100644 index 59e7d8ed69c1..000000000000 --- a/cmds/idmap2/tests/data/signature-overlay/res/values/values.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <!-- This overlay will fulfill the policy "signature". This allows it overlay the - following resources. --> - <string name="policy_signature">policy_signature</string> -</resources> diff --git a/cmds/idmap2/tests/data/signature-overlay/signature-overlay.apk b/cmds/idmap2/tests/data/signature-overlay/signature-overlay.apk Binary files differdeleted file mode 100644 index e0fd20499671..000000000000 --- a/cmds/idmap2/tests/data/signature-overlay/signature-overlay.apk +++ /dev/null diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/AndroidManifest.xml b/cmds/idmap2/tests/data/system-overlay-invalid/AndroidManifest.xml deleted file mode 100644 index c7b652cdb287..000000000000 --- a/cmds/idmap2/tests/data/system-overlay-invalid/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<manifest - xmlns:android="http://schemas.android.com/apk/res/android" - package="test.overlay.system.invalid"> - <application android:hasCode="false"/> - <overlay - android:targetPackage="test.target" - android:targetName="TestResources"/> -</manifest> diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/build b/cmds/idmap2/tests/data/system-overlay-invalid/build deleted file mode 100755 index 920e1f8ad6f3..000000000000 --- a/cmds/idmap2/tests/data/system-overlay-invalid/build +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (C) 2019 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -FRAMEWORK_RES_APK=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar - -aapt2 compile --dir res -o compiled.flata - -aapt2 link \ - --no-resource-removal \ - -I "$FRAMEWORK_RES_APK" \ - --manifest AndroidManifest.xml \ - -o system-overlay-invalid.apk \ - compiled.flata - -rm compiled.flata diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml b/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml deleted file mode 100644 index ebaf49c34762..000000000000 --- a/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <!-- This overlay will fulfill the policies "public|system". This allows it overlay the - following resources. --> - <string name="policy_system">policy_system</string> - <string name="policy_system_vendor">policy_system_vendor</string> - <string name="policy_public">policy_public</string> - - <!-- Requests to overlay a resource that belongs to a policy the overlay does not fulfill. --> - <string name="policy_product">policy_product</string> - <string name="policy_signature">policy_signature</string> - <string name="policy_odm">policy_odm</string> - <string name="policy_oem">policy_oem</string> - <string name="policy_actor">policy_actor</string> - <string name="policy_config_signature">policy_config_signature</string> - - <!-- Requests to overlay a resource that is not declared as overlayable. --> - <string name="not_overlayable">not_overlayable</string> - - <!-- Requests to overlay a resource that is defined in an overlayable with a name other than - the targetName in the manifest. --> - <string name="other">other</string> -</resources> diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk b/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk Binary files differdeleted file mode 100644 index a63daf86caf5..000000000000 --- a/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk +++ /dev/null diff --git a/cmds/idmap2/tests/data/system-overlay/build b/cmds/idmap2/tests/data/system-overlay/build deleted file mode 100755 index be0d2390f535..000000000000 --- a/cmds/idmap2/tests/data/system-overlay/build +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (C) 2019 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -FRAMEWORK_RES_APK=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar - -aapt2 compile --dir res -o compiled.flata - -aapt2 link \ - --no-resource-removal \ - -I "$FRAMEWORK_RES_APK" \ - --manifest AndroidManifest.xml \ - -o system-overlay.apk \ - compiled.flata - -rm compiled.flata diff --git a/cmds/idmap2/tests/data/system-overlay/res/values/values.xml b/cmds/idmap2/tests/data/system-overlay/res/values/values.xml deleted file mode 100644 index 6aaa0b02639e..000000000000 --- a/cmds/idmap2/tests/data/system-overlay/res/values/values.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <!-- This overlay will fulfill the policies "public|system". This allows it overlay the - following resources. --> - <string name="policy_system">policy_system</string> - <string name="policy_system_vendor">policy_system_vendor</string> - <string name="policy_public">policy_public</string> -</resources> diff --git a/cmds/idmap2/tests/data/system-overlay/system-overlay.apk b/cmds/idmap2/tests/data/system-overlay/system-overlay.apk Binary files differdeleted file mode 100644 index 90d2803a1eca..000000000000 --- a/cmds/idmap2/tests/data/system-overlay/system-overlay.apk +++ /dev/null diff --git a/config/hiddenapi-temp-blocklist.txt b/config/hiddenapi-temp-blocklist.txt index 246eeea35a19..753bc69be95b 100644 --- a/config/hiddenapi-temp-blocklist.txt +++ b/config/hiddenapi-temp-blocklist.txt @@ -47,9 +47,6 @@ Lcom/android/ims/internal/uce/uceservice/IUceListener$Stub;-><init>()V Lcom/android/ims/internal/uce/uceservice/IUceService$Stub;-><init>()V Lcom/android/internal/app/IVoiceInteractionManagerService$Stub$Proxy;->showSessionFromSession(Landroid/os/IBinder;Landroid/os/Bundle;I)Z Lcom/android/internal/appwidget/IAppWidgetService$Stub;->TRANSACTION_bindAppWidgetId:I -Lcom/android/internal/location/ILocationProvider$Stub;-><init>()V -Lcom/android/internal/location/ILocationProviderManager$Stub;-><init>()V -Lcom/android/internal/location/ILocationProviderManager$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/location/ILocationProviderManager; Lcom/android/internal/telephony/ITelephony$Stub;->DESCRIPTOR:Ljava/lang/String; Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_dial:I Lcom/android/internal/widget/IRemoteViewsFactory$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/widget/IRemoteViewsFactory; diff --git a/config/hiddenapi-unsupported.txt b/config/hiddenapi-unsupported.txt index 8a377ac051b0..90a526bcfaf7 100644 --- a/config/hiddenapi-unsupported.txt +++ b/config/hiddenapi-unsupported.txt @@ -269,7 +269,6 @@ Lcom/android/internal/app/IMediaContainerService$Stub;->asInterface(Landroid/os/ Lcom/android/internal/app/IVoiceInteractionManagerService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/app/IVoiceInteractionManagerService; Lcom/android/internal/appwidget/IAppWidgetService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/appwidget/IAppWidgetService; Lcom/android/internal/backup/IBackupTransport$Stub;-><init>()V -Lcom/android/internal/location/ILocationProvider$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/location/ILocationProvider; Lcom/android/internal/os/IDropBoxManagerService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/os/IDropBoxManagerService; Lcom/android/internal/policy/IKeyguardService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/policy/IKeyguardService; Lcom/android/internal/policy/IKeyguardStateCallback$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/policy/IKeyguardStateCallback; diff --git a/core/api/current.txt b/core/api/current.txt index a398caffbda0..8964c9c8a6cf 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -6925,6 +6925,7 @@ package android.app.admin { 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 @@ package android.app.admin { 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); @@ -7135,7 +7137,7 @@ package android.app.admin { field public static final String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED"; field public static final String ACTION_PROFILE_OWNER_CHANGED = "android.app.action.PROFILE_OWNER_CHANGED"; field public static final String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL"; - field public static final String ACTION_PROVISION_MANAGED_DEVICE = "android.app.action.PROVISION_MANAGED_DEVICE"; + field @Deprecated public static final String ACTION_PROVISION_MANAGED_DEVICE = "android.app.action.PROVISION_MANAGED_DEVICE"; field public static final String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.PROVISION_MANAGED_PROFILE"; field public static final String ACTION_SET_NEW_PARENT_PROFILE_PASSWORD = "android.app.action.SET_NEW_PARENT_PROFILE_PASSWORD"; field public static final String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD"; @@ -7186,7 +7188,7 @@ package android.app.admin { field public static final String EXTRA_PROVISIONING_SERIAL_NUMBER = "android.app.extra.PROVISIONING_SERIAL_NUMBER"; field public static final String EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS = "android.app.extra.PROVISIONING_SKIP_EDUCATION_SCREENS"; field public static final String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION"; - field public static final String EXTRA_PROVISIONING_SKIP_USER_CONSENT = "android.app.extra.PROVISIONING_SKIP_USER_CONSENT"; + field @Deprecated public static final String EXTRA_PROVISIONING_SKIP_USER_CONSENT = "android.app.extra.PROVISIONING_SKIP_USER_CONSENT"; field public static final String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE"; field public static final String EXTRA_PROVISIONING_WIFI_ANONYMOUS_IDENTITY = "android.app.extra.PROVISIONING_WIFI_ANONYMOUS_IDENTITY"; field public static final String EXTRA_PROVISIONING_WIFI_CA_CERTIFICATE = "android.app.extra.PROVISIONING_WIFI_CA_CERTIFICATE"; @@ -7492,6 +7494,7 @@ package android.app.assist { method public int getMaxTextEms(); method public int getMaxTextLength(); method public int getMinTextEms(); + method @Nullable public String[] getOnReceiveContentMimeTypes(); method public int getScrollX(); method public int getScrollY(); method @Nullable public CharSequence getText(); @@ -7826,6 +7829,52 @@ package android.app.job { } +package android.app.people { + + public final class ConversationStatus implements android.os.Parcelable { + method public int describeContents(); + method public int getActivity(); + method public int getAvailability(); + method @Nullable public CharSequence getDescription(); + method public long getEndTimeMillis(); + method @Nullable public android.graphics.drawable.Icon getIcon(); + method @NonNull public String getId(); + method public long getStartTimeMillis(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int ACTIVITY_ANNIVERSARY = 2; // 0x2 + field public static final int ACTIVITY_BIRTHDAY = 1; // 0x1 + field public static final int ACTIVITY_GAME = 5; // 0x5 + field public static final int ACTIVITY_LOCATION = 6; // 0x6 + field public static final int ACTIVITY_MEDIA = 4; // 0x4 + field public static final int ACTIVITY_NEW_STORY = 3; // 0x3 + field public static final int ACTIVITY_OTHER = 0; // 0x0 + field public static final int ACTIVITY_UPCOMING_BIRTHDAY = 7; // 0x7 + field public static final int AVAILABILITY_AVAILABLE = 0; // 0x0 + field public static final int AVAILABILITY_BUSY = 1; // 0x1 + field public static final int AVAILABILITY_OFFLINE = 2; // 0x2 + field public static final int AVAILABILITY_UNKNOWN = -1; // 0xffffffff + field @NonNull public static final android.os.Parcelable.Creator<android.app.people.ConversationStatus> CREATOR; + } + + public static final class ConversationStatus.Builder { + ctor public ConversationStatus.Builder(@NonNull String, @NonNull int); + method @NonNull public android.app.people.ConversationStatus build(); + method @NonNull public android.app.people.ConversationStatus.Builder setAvailability(int); + method @NonNull public android.app.people.ConversationStatus.Builder setDescription(@Nullable CharSequence); + method @NonNull public android.app.people.ConversationStatus.Builder setEndTimeMillis(long); + method @NonNull public android.app.people.ConversationStatus.Builder setIcon(@Nullable android.graphics.drawable.Icon); + method @NonNull public android.app.people.ConversationStatus.Builder setStartTimeMillis(long); + } + + public final class PeopleManager { + method public void addOrUpdateStatus(@NonNull String, @NonNull android.app.people.ConversationStatus); + method public void clearStatus(@NonNull String, @NonNull String); + method public void clearStatuses(@NonNull String); + method @NonNull public java.util.List<android.app.people.ConversationStatus> getStatuses(@NonNull String); + } + +} + package android.app.role { public final class RoleManager { @@ -10315,6 +10364,7 @@ package android.content { field public static final String NFC_SERVICE = "nfc"; field public static final String NOTIFICATION_SERVICE = "notification"; field public static final String NSD_SERVICE = "servicediscovery"; + field public static final String PEOPLE_SERVICE = "people"; field public static final String POWER_SERVICE = "power"; field public static final String PRINT_SERVICE = "print"; field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1 @@ -12327,6 +12377,7 @@ package android.content.pm { 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"; @@ -18942,7 +18993,9 @@ package android.location { } public static class GnssAntennaInfo.Builder { - ctor public GnssAntennaInfo.Builder(); + ctor @Deprecated public GnssAntennaInfo.Builder(); + ctor public GnssAntennaInfo.Builder(double, @NonNull android.location.GnssAntennaInfo.PhaseCenterOffset); + ctor public GnssAntennaInfo.Builder(@NonNull android.location.GnssAntennaInfo); method @NonNull public android.location.GnssAntennaInfo build(); method @NonNull public android.location.GnssAntennaInfo.Builder setCarrierFrequencyMHz(@FloatRange(from=0.0f) double); method @NonNull public android.location.GnssAntennaInfo.Builder setPhaseCenterOffset(@NonNull android.location.GnssAntennaInfo.PhaseCenterOffset); @@ -18950,8 +19003,8 @@ package android.location { method @NonNull public android.location.GnssAntennaInfo.Builder setSignalGainCorrections(@Nullable android.location.GnssAntennaInfo.SphericalCorrections); } - public static interface GnssAntennaInfo.Listener { - method public void onGnssAntennaInfoReceived(@NonNull java.util.List<android.location.GnssAntennaInfo>); + @Deprecated public static interface GnssAntennaInfo.Listener { + method @Deprecated public void onGnssAntennaInfoReceived(@NonNull java.util.List<android.location.GnssAntennaInfo>); } public static final class GnssAntennaInfo.PhaseCenterOffset implements android.os.Parcelable { @@ -19124,11 +19177,11 @@ package android.location { public abstract static class GnssMeasurementsEvent.Callback { ctor public GnssMeasurementsEvent.Callback(); method public void onGnssMeasurementsReceived(android.location.GnssMeasurementsEvent); - method public void onStatusChanged(int); - field public static final int STATUS_LOCATION_DISABLED = 2; // 0x2 - field public static final int STATUS_NOT_ALLOWED = 3; // 0x3 - field public static final int STATUS_NOT_SUPPORTED = 0; // 0x0 - field public static final int STATUS_READY = 1; // 0x1 + method @Deprecated public void onStatusChanged(int); + field @Deprecated public static final int STATUS_LOCATION_DISABLED = 2; // 0x2 + field @Deprecated public static final int STATUS_NOT_ALLOWED = 3; // 0x3 + field @Deprecated public static final int STATUS_NOT_SUPPORTED = 0; // 0x0 + field @Deprecated public static final int STATUS_READY = 1; // 0x1 } public final class GnssNavigationMessage implements android.os.Parcelable { @@ -19164,10 +19217,10 @@ package android.location { public abstract static class GnssNavigationMessage.Callback { ctor public GnssNavigationMessage.Callback(); method public void onGnssNavigationMessageReceived(android.location.GnssNavigationMessage); - method public void onStatusChanged(int); - field public static final int STATUS_LOCATION_DISABLED = 2; // 0x2 - field public static final int STATUS_NOT_SUPPORTED = 0; // 0x0 - field public static final int STATUS_READY = 1; // 0x1 + method @Deprecated public void onStatusChanged(int); + field @Deprecated public static final int STATUS_LOCATION_DISABLED = 2; // 0x2 + field @Deprecated public static final int STATUS_NOT_SUPPORTED = 0; // 0x0 + field @Deprecated public static final int STATUS_READY = 1; // 0x1 } public final class GnssStatus implements android.os.Parcelable { @@ -19318,6 +19371,7 @@ package android.location { method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.OnNmeaMessageListener); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void addProximityAlert(double, double, float, long, @NonNull android.app.PendingIntent); method public void addTestProvider(@NonNull String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int); + method public void addTestProvider(@NonNull String, @NonNull android.location.provider.ProviderProperties); method @Deprecated public void clearTestProviderEnabled(@NonNull String); method @Deprecated public void clearTestProviderLocation(@NonNull String); method @Deprecated public void clearTestProviderStatus(@NonNull String); @@ -19325,19 +19379,20 @@ package android.location { method @Nullable public String getBestProvider(@NonNull android.location.Criteria, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void getCurrentLocation(@NonNull String, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.location.Location>); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void getCurrentLocation(@NonNull String, @NonNull android.location.LocationRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.location.Location>); + method @Nullable public java.util.List<android.location.GnssAntennaInfo> getGnssAntennaInfos(); method @NonNull public android.location.GnssCapabilities getGnssCapabilities(); method @Nullable public String getGnssHardwareModelName(); method public int getGnssYearOfHardware(); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public android.location.GpsStatus getGpsStatus(@Nullable android.location.GpsStatus); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.location.Location getLastKnownLocation(@NonNull String); method @Deprecated @Nullable public android.location.LocationProvider getProvider(@NonNull String); - method @Nullable public android.location.ProviderProperties getProviderProperties(@NonNull String); + method @Nullable public android.location.provider.ProviderProperties getProviderProperties(@NonNull String); method @NonNull public java.util.List<java.lang.String> getProviders(boolean); method @NonNull public java.util.List<java.lang.String> getProviders(@NonNull android.location.Criteria, boolean); method public boolean hasProvider(@NonNull String); method public boolean isLocationEnabled(); method public boolean isProviderEnabled(@NonNull String); - method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerAntennaInfoListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssAntennaInfo.Listener); + method @Deprecated public boolean registerAntennaInfoListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssAntennaInfo.Listener); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback); method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback, @Nullable android.os.Handler); method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback); @@ -19374,10 +19429,14 @@ package android.location { method public void setTestProviderEnabled(@NonNull String, boolean); method public void setTestProviderLocation(@NonNull String, @NonNull android.location.Location); method @Deprecated public void setTestProviderStatus(@NonNull String, int, @Nullable android.os.Bundle, long); - method public void unregisterAntennaInfoListener(@NonNull android.location.GnssAntennaInfo.Listener); + method @Deprecated public void unregisterAntennaInfoListener(@NonNull android.location.GnssAntennaInfo.Listener); method public void unregisterGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback); method public void unregisterGnssNavigationMessageCallback(@NonNull android.location.GnssNavigationMessage.Callback); method public void unregisterGnssStatusCallback(@NonNull android.location.GnssStatus.Callback); + field public static final String ACTION_GNSS_ANTENNA_INFOS_CHANGED = "android.location.action.GNSS_ANTENNA_INFOS_CHANGED"; + field public static final String ACTION_GNSS_CAPABILITIES_CHANGED = "android.location.action.GNSS_CAPABILITIES_CHANGED"; + field public static final String EXTRA_GNSS_ANTENNA_INFOS = "android.location.extra.GNSS_ANTENNA_INFOS"; + field public static final String EXTRA_GNSS_CAPABILITIES = "android.location.extra.GNSS_CAPABILITIES"; field public static final String EXTRA_LOCATION_ENABLED = "android.location.extra.LOCATION_ENABLED"; field public static final String EXTRA_PROVIDER_ENABLED = "android.location.extra.PROVIDER_ENABLED"; field public static final String EXTRA_PROVIDER_NAME = "android.location.extra.PROVIDER_NAME"; @@ -19459,6 +19518,24 @@ package android.location { method public void onNmeaMessage(String, long); } + public abstract class SettingInjectorService extends android.app.Service { + ctor public SettingInjectorService(String); + method public final android.os.IBinder onBind(android.content.Intent); + method protected abstract boolean onGetEnabled(); + method protected abstract String onGetSummary(); + method public final void onStart(android.content.Intent, int); + method public final int onStartCommand(android.content.Intent, int, int); + method public static final void refreshSettings(@NonNull android.content.Context); + field public static final String ACTION_INJECTED_SETTING_CHANGED = "android.location.InjectedSettingChanged"; + field public static final String ACTION_SERVICE_INTENT = "android.location.SettingInjectorService"; + field public static final String ATTRIBUTES_NAME = "injected-location-setting"; + field public static final String META_DATA_NAME = "android.location.SettingInjectorService"; + } + +} + +package android.location.provider { + public final class ProviderProperties implements android.os.Parcelable { method public int describeContents(); method public int getAccuracy(); @@ -19473,24 +19550,25 @@ package android.location { method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int ACCURACY_COARSE = 2; // 0x2 field public static final int ACCURACY_FINE = 1; // 0x1 - field @NonNull public static final android.os.Parcelable.Creator<android.location.ProviderProperties> CREATOR; + field @NonNull public static final android.os.Parcelable.Creator<android.location.provider.ProviderProperties> CREATOR; field public static final int POWER_USAGE_HIGH = 3; // 0x3 field public static final int POWER_USAGE_LOW = 1; // 0x1 field public static final int POWER_USAGE_MEDIUM = 2; // 0x2 } - public abstract class SettingInjectorService extends android.app.Service { - ctor public SettingInjectorService(String); - method public final android.os.IBinder onBind(android.content.Intent); - method protected abstract boolean onGetEnabled(); - method protected abstract String onGetSummary(); - method public final void onStart(android.content.Intent, int); - method public final int onStartCommand(android.content.Intent, int, int); - method public static final void refreshSettings(@NonNull android.content.Context); - field public static final String ACTION_INJECTED_SETTING_CHANGED = "android.location.InjectedSettingChanged"; - field public static final String ACTION_SERVICE_INTENT = "android.location.SettingInjectorService"; - field public static final String ATTRIBUTES_NAME = "injected-location-setting"; - field public static final String META_DATA_NAME = "android.location.SettingInjectorService"; + public static final class ProviderProperties.Builder { + ctor public ProviderProperties.Builder(); + ctor public ProviderProperties.Builder(@NonNull android.location.provider.ProviderProperties); + method @NonNull public android.location.provider.ProviderProperties build(); + method @NonNull public android.location.provider.ProviderProperties.Builder setAccuracy(int); + method @NonNull public android.location.provider.ProviderProperties.Builder setHasAltitudeSupport(boolean); + method @NonNull public android.location.provider.ProviderProperties.Builder setHasBearingSupport(boolean); + method @NonNull public android.location.provider.ProviderProperties.Builder setHasCellRequirement(boolean); + method @NonNull public android.location.provider.ProviderProperties.Builder setHasMonetaryCost(boolean); + method @NonNull public android.location.provider.ProviderProperties.Builder setHasNetworkRequirement(boolean); + method @NonNull public android.location.provider.ProviderProperties.Builder setHasSatelliteRequirement(boolean); + method @NonNull public android.location.provider.ProviderProperties.Builder setHasSpeedSupport(boolean); + method @NonNull public android.location.provider.ProviderProperties.Builder setPowerUsage(int); } } @@ -31357,6 +31435,7 @@ package android.os { 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 @@ -39768,6 +39847,8 @@ package android.telephony { method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle); method public void notifyConfigChangedForSubId(int); field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; + field public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_ONLY = 0; // 0x0 + field public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING = 1; // 0x1 field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff field public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool"; @@ -39809,6 +39890,7 @@ package android.telephony { field public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY = "carrier_certificate_string_array"; field public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool"; field public static final String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string"; + field public static final String KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL = "carrier_cross_sim_ims_available_bool"; field public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings"; field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY = "carrier_default_actions_on_dcfailure_string_array"; field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE = "carrier_default_actions_on_default_network_available_string_array"; @@ -39860,6 +39942,7 @@ package android.telephony { field public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string"; field public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool"; field public static final String KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm"; + field public static final String KEY_CROSS_SIM_SPN_FORMAT_INT = "cross_sim_spn_format_int"; field public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool"; field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool"; field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long"; @@ -45379,6 +45462,7 @@ package android.util { 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); @@ -48686,6 +48770,7 @@ package android.view { method public void setMaxTextEms(int); method public void setMaxTextLength(int); method public void setMinTextEms(int); + method public void setOnReceiveContentMimeTypes(@Nullable String[]); method public abstract void setOpaque(boolean); method public abstract void setSelected(boolean); method public abstract void setText(CharSequence); @@ -50457,6 +50542,7 @@ package android.view.inputmethod { method public int describeContents(); method public void dump(android.util.Printer, String); method @Nullable public CharSequence getInitialSelectedText(int); + method @Nullable public android.view.inputmethod.SurroundingText getInitialSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int); method @Nullable public CharSequence getInitialTextAfterCursor(int, int); method @Nullable public CharSequence getInitialTextBeforeCursor(int, int); method public final void makeCompatible(int); @@ -50620,6 +50706,7 @@ package android.view.inputmethod { method public boolean sendKeyEvent(android.view.KeyEvent); method public boolean setComposingRegion(int, int); method public boolean setComposingText(CharSequence, int); + method public default boolean setImeTemporarilyConsumesInput(boolean); method public boolean setSelection(int, int); field public static final int CURSOR_UPDATE_IMMEDIATE = 1; // 0x1 field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2 @@ -51526,6 +51613,66 @@ package android.view.textservice { } +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 { @@ -54037,7 +54184,7 @@ package android.widget { ctor public RemoteViews(android.widget.RemoteViews, android.widget.RemoteViews); ctor public RemoteViews(android.widget.RemoteViews); ctor public RemoteViews(android.os.Parcel); - method public void addView(int, android.widget.RemoteViews); + method public void addView(@IdRes int, android.widget.RemoteViews); method public android.view.View apply(android.content.Context, android.view.ViewGroup); method @Deprecated public android.widget.RemoteViews clone(); method public int describeContents(); @@ -54045,53 +54192,53 @@ package android.widget { method public String getPackage(); method @Deprecated public boolean onLoadClass(Class); method public void reapply(android.content.Context, android.view.View); - method public void removeAllViews(int); - method public void setAccessibilityTraversalAfter(int, int); - method public void setAccessibilityTraversalBefore(int, int); - method public void setBitmap(int, String, android.graphics.Bitmap); - method public void setBoolean(int, String, boolean); - method public void setBundle(int, String, android.os.Bundle); - method public void setByte(int, String, byte); - method public void setChar(int, String, char); - method public void setCharSequence(int, String, CharSequence); - method public void setChronometer(int, long, String, boolean); - method public void setChronometerCountDown(int, boolean); - method public void setContentDescription(int, CharSequence); - method public void setDisplayedChild(int, int); - method public void setDouble(int, String, double); - method public void setEmptyView(int, int); - method public void setFloat(int, String, float); - method public void setIcon(int, String, android.graphics.drawable.Icon); - method public void setImageViewBitmap(int, android.graphics.Bitmap); - method public void setImageViewIcon(int, android.graphics.drawable.Icon); - method public void setImageViewResource(int, int); - method public void setImageViewUri(int, android.net.Uri); - method public void setInt(int, String, int); - method public void setIntent(int, String, android.content.Intent); - method public void setLabelFor(int, int); + method public void removeAllViews(@IdRes int); + method public void setAccessibilityTraversalAfter(@IdRes int, @IdRes int); + method public void setAccessibilityTraversalBefore(@IdRes int, @IdRes int); + method public void setBitmap(@IdRes int, String, android.graphics.Bitmap); + method public void setBoolean(@IdRes int, String, boolean); + method public void setBundle(@IdRes int, String, android.os.Bundle); + method public void setByte(@IdRes int, String, byte); + method public void setChar(@IdRes int, String, char); + method public void setCharSequence(@IdRes int, String, CharSequence); + method public void setChronometer(@IdRes int, long, String, boolean); + method public void setChronometerCountDown(@IdRes int, boolean); + method public void setContentDescription(@IdRes int, CharSequence); + method public void setDisplayedChild(@IdRes int, int); + method public void setDouble(@IdRes int, String, double); + method public void setEmptyView(@IdRes int, @IdRes int); + method public void setFloat(@IdRes int, String, float); + method public void setIcon(@IdRes int, String, android.graphics.drawable.Icon); + method public void setImageViewBitmap(@IdRes int, android.graphics.Bitmap); + method public void setImageViewIcon(@IdRes int, android.graphics.drawable.Icon); + method public void setImageViewResource(@IdRes int, @DrawableRes int); + method public void setImageViewUri(@IdRes int, android.net.Uri); + method public void setInt(@IdRes int, String, int); + method public void setIntent(@IdRes int, String, android.content.Intent); + method public void setLabelFor(@IdRes int, @IdRes int); method public void setLightBackgroundLayoutId(@LayoutRes int); - method public void setLong(int, String, long); - method public void setOnClickFillInIntent(int, android.content.Intent); - method public void setOnClickPendingIntent(int, android.app.PendingIntent); - method public void setOnClickResponse(int, @NonNull android.widget.RemoteViews.RemoteResponse); - method public void setPendingIntentTemplate(int, android.app.PendingIntent); - method public void setProgressBar(int, int, int, boolean); - method public void setRelativeScrollPosition(int, int); - method @Deprecated public void setRemoteAdapter(int, int, android.content.Intent); - method public void setRemoteAdapter(int, android.content.Intent); - method public void setScrollPosition(int, int); - method public void setShort(int, String, short); - method public void setString(int, String, String); - method public void setTextColor(int, @ColorInt int); - method public void setTextViewCompoundDrawables(int, int, int, int, int); - method public void setTextViewCompoundDrawablesRelative(int, int, int, int, int); - method public void setTextViewText(int, CharSequence); - method public void setTextViewTextSize(int, int, float); - method public void setUri(int, String, android.net.Uri); - method public void setViewPadding(int, int, int, int, int); - method public void setViewVisibility(int, int); - method public void showNext(int); - method public void showPrevious(int); + method public void setLong(@IdRes int, String, long); + method public void setOnClickFillInIntent(@IdRes int, android.content.Intent); + method public void setOnClickPendingIntent(@IdRes int, android.app.PendingIntent); + method public void setOnClickResponse(@IdRes int, @NonNull android.widget.RemoteViews.RemoteResponse); + method public void setPendingIntentTemplate(@IdRes int, android.app.PendingIntent); + method public void setProgressBar(@IdRes int, int, int, boolean); + method public void setRelativeScrollPosition(@IdRes int, int); + method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent); + method public void setRemoteAdapter(@IdRes int, android.content.Intent); + method public void setScrollPosition(@IdRes int, int); + method public void setShort(@IdRes int, String, short); + method public void setString(@IdRes int, String, String); + method public void setTextColor(@IdRes int, @ColorInt int); + method public void setTextViewCompoundDrawables(@IdRes int, @DrawableRes int, @DrawableRes int, @DrawableRes int, @DrawableRes int); + method public void setTextViewCompoundDrawablesRelative(@IdRes int, @DrawableRes int, @DrawableRes int, @DrawableRes int, @DrawableRes int); + method public void setTextViewText(@IdRes int, CharSequence); + method public void setTextViewTextSize(@IdRes int, int, float); + method public void setUri(@IdRes int, String, android.net.Uri); + method public void setViewPadding(@IdRes int, @Px int, @Px int, @Px int, @Px int); + method public void setViewVisibility(@IdRes int, int); + method public void showNext(@IdRes int); + method public void showPrevious(@IdRes int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.widget.RemoteViews> CREATOR; field public static final String EXTRA_SHARED_ELEMENT_BOUNDS = "android.widget.extra.SHARED_ELEMENT_BOUNDS"; @@ -54104,7 +54251,7 @@ package android.widget { public static class RemoteViews.RemoteResponse { ctor public RemoteViews.RemoteResponse(); - method @NonNull public android.widget.RemoteViews.RemoteResponse addSharedElement(int, @NonNull String); + method @NonNull public android.widget.RemoteViews.RemoteResponse addSharedElement(@IdRes int, @NonNull String); method @NonNull public static android.widget.RemoteViews.RemoteResponse fromFillInIntent(@NonNull android.content.Intent); method @NonNull public static android.widget.RemoteViews.RemoteResponse fromPendingIntent(@NonNull android.app.PendingIntent); } diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index f22b0f4b22ad..ef6c8b713c2c 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -109,8 +109,11 @@ package android.media.session { public final class MediaSessionManager { method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName, int, @Nullable android.os.Handler); + method public void dispatchMediaKeyEvent(@NonNull android.view.KeyEvent); + method public void dispatchMediaKeyEvent(@NonNull android.view.KeyEvent, boolean); method public void dispatchMediaKeyEventAsSystemService(@NonNull android.view.KeyEvent); method public boolean dispatchMediaKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token); + method public void dispatchVolumeKeyEvent(@NonNull android.view.KeyEvent, int, boolean); method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int); method public void dispatchVolumeKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token); method @NonNull public java.util.List<android.media.session.MediaController> getActiveSessionsForUser(@Nullable android.content.ComponentName, int); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 8b7e38878b4a..86ef2e4e74bb 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -52,6 +52,7 @@ package android { 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"; @@ -661,6 +662,10 @@ package android.app { field public static final int FLAG_AUTOGROUP_SUMMARY = 1024; // 0x400 } + public static class Notification.Action implements android.os.Parcelable { + field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb + } + public static final class Notification.TvExtender implements android.app.Notification.Extender { ctor public Notification.TvExtender(); ctor public Notification.TvExtender(android.app.Notification); @@ -902,6 +907,7 @@ package android.app.admin { 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 @@ -1801,6 +1807,7 @@ package android.bluetooth { field @NonNull public static final android.os.ParcelUuid AVRCP_TARGET; field @NonNull public static final android.os.ParcelUuid BASE_UUID; field @NonNull public static final android.os.ParcelUuid BNEP; + field @NonNull public static final android.os.ParcelUuid DIP; field @NonNull public static final android.os.ParcelUuid HEARING_AID; field @NonNull public static final android.os.ParcelUuid HFP; field @NonNull public static final android.os.ParcelUuid HFP_AG; @@ -1935,6 +1942,7 @@ package android.content { field public static final String SYSTEM_CONFIG_SERVICE = "system_config"; field public static final String SYSTEM_UPDATE_SERVICE = "system_update"; field public static final String TETHERING_SERVICE = "tethering"; + field public static final String TRANSLATION_MANAGER_SERVICE = "transformer"; field public static final String VR_SERVICE = "vrmanager"; field public static final String WIFI_NL80211_SERVICE = "wifinl80211"; field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager"; @@ -4375,6 +4383,56 @@ package android.location { } +package android.location.provider { + + public abstract class LocationProviderBase { + ctor public LocationProviderBase(@NonNull android.content.Context, @NonNull String, @NonNull android.location.provider.ProviderProperties); + method @Nullable public final android.os.IBinder getBinder(); + method @NonNull public android.location.provider.ProviderProperties getProperties(); + method public boolean isAllowed(); + method public abstract void onFlush(@NonNull android.location.provider.LocationProviderBase.OnFlushCompleteCallback); + method public abstract void onSendExtraCommand(@NonNull String, @Nullable android.os.Bundle); + method public abstract void onSetRequest(@NonNull android.location.provider.ProviderRequest); + method public void reportLocation(@NonNull android.location.Location); + method public void reportLocation(@NonNull android.location.LocationResult); + method public void setAllowed(boolean); + method public void setProperties(@NonNull android.location.provider.ProviderProperties); + field public static final String ACTION_FUSED_PROVIDER = "com.android.location.service.FusedLocationProvider"; + field public static final String ACTION_NETWORK_PROVIDER = "com.android.location.service.v3.NetworkLocationProvider"; + } + + public static interface LocationProviderBase.OnFlushCompleteCallback { + method public void onFlushComplete(); + } + + public final class ProviderRequest implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0) public long getIntervalMillis(); + method @IntRange(from=0) public long getMaxUpdateDelayMillis(); + method public int getQuality(); + method @NonNull public android.os.WorkSource getWorkSource(); + method public boolean isActive(); + method public boolean isLocationSettingsIgnored(); + method public boolean isLowPower(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.provider.ProviderRequest> CREATOR; + field @NonNull public static final android.location.provider.ProviderRequest EMPTY_REQUEST; + field public static final long INTERVAL_DISABLED = 9223372036854775807L; // 0x7fffffffffffffffL + } + + public static final class ProviderRequest.Builder { + ctor public ProviderRequest.Builder(); + method @NonNull public android.location.provider.ProviderRequest build(); + method @NonNull public android.location.provider.ProviderRequest.Builder setIntervalMillis(@IntRange(from=0) long); + method @NonNull public android.location.provider.ProviderRequest.Builder setLocationSettingsIgnored(boolean); + method @NonNull public android.location.provider.ProviderRequest.Builder setLowPower(boolean); + method @NonNull public android.location.provider.ProviderRequest.Builder setMaxUpdateDelayMillis(@IntRange(from=0) long); + method @NonNull public android.location.provider.ProviderRequest.Builder setQuality(int); + method @NonNull public android.location.provider.ProviderRequest.Builder setWorkSource(@NonNull android.os.WorkSource); + } + +} + package android.media { public final class AudioAttributes implements android.os.Parcelable { @@ -8561,6 +8619,7 @@ package android.provider { field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager"; field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot"; field public static final String NAMESPACE_APP_COMPAT = "app_compat"; + field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation"; field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service"; field public static final String NAMESPACE_AUTOFILL = "autofill"; field public static final String NAMESPACE_BATTERY_SAVER = "battery_saver"; @@ -9212,6 +9271,7 @@ package android.service.autofill.augmented { method @NonNull public android.content.ComponentName getActivityComponent(); method @NonNull public android.view.autofill.AutofillId getFocusedId(); method @NonNull public android.view.autofill.AutofillValue getFocusedValue(); + method @Nullable public android.app.assist.AssistStructure.ViewNode getFocusedViewNode(); method @Nullable public android.view.inputmethod.InlineSuggestionsRequest getInlineSuggestionsRequest(); method @Nullable public android.service.autofill.augmented.PresentationParams getPresentationParams(); method public int getTaskId(); @@ -9796,6 +9856,48 @@ package android.service.timezone { } +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 { @@ -9851,6 +9953,7 @@ package android.service.voice { field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8 field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1 + field public static final int STATE_ERROR = 3; // 0x3 field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2 field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 4ef24ff18c48..c03461aa7742 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -18,6 +18,7 @@ package android { field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES"; field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS"; field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS"; + field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING"; field public static final String MODIFY_REFRESH_RATE_SWITCHING_TYPE = "android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE"; field public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS"; field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK"; @@ -138,7 +139,6 @@ package android.app { method public static boolean currentUiModeSupportsErrorDialogs(@NonNull android.content.Context); method public static int getMaxNumPictureInPictureActions(@NonNull android.content.Context); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void moveTaskToRootTask(int, int, boolean); - method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean moveTopActivityToPinnedRootTask(int, @NonNull android.graphics.Rect); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void removeRootTasksInWindowingModes(@NonNull int[]); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void removeRootTasksWithActivityTypes(@NonNull int[]); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void resizePrimarySplitScreen(@NonNull android.graphics.Rect, @NonNull android.graphics.Rect); @@ -275,9 +275,14 @@ package android.app { method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String); method public boolean matchesCallFilter(android.os.Bundle); method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean); + method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean); method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel); } + public final class PendingIntent implements android.os.Parcelable { + field @Deprecated public static final int FLAG_MUTABLE_UNAUDITED = 33554432; // 0x2000000 + } + public final class PictureInPictureParams implements android.os.Parcelable { method public java.util.List<android.app.RemoteAction> getActions(); method public float getAspectRatio(); @@ -292,7 +297,9 @@ package android.app { } public class TaskInfo { + method public boolean containsLaunchCookie(@NonNull android.os.IBinder); method @NonNull public android.content.res.Configuration getConfiguration(); + method public int getParentTaskId(); method @Nullable public android.app.PictureInPictureParams getPictureInPictureParams(); method @NonNull public android.window.WindowContainerToken getToken(); method public boolean hasParentTask(); @@ -368,6 +375,7 @@ package android.app { package android.app.admin { public class DevicePolicyManager { + method public void forceUpdateUserSetupComplete(); method public long getLastBugReportRequestTime(); method public long getLastNetworkLogRetrievalTime(); method public long getLastSecurityLogRetrievalTime(); @@ -1714,6 +1722,7 @@ package android.service.autofill.augmented { method @NonNull public android.content.ComponentName getActivityComponent(); method @NonNull public android.view.autofill.AutofillId getFocusedId(); method @NonNull public android.view.autofill.AutofillValue getFocusedValue(); + method @Nullable public android.app.assist.AssistStructure.ViewNode getFocusedViewNode(); method @Nullable public android.view.inputmethod.InlineSuggestionsRequest getInlineSuggestionsRequest(); method @Nullable public android.service.autofill.augmented.PresentationParams getPresentationParams(); method public int getTaskId(); @@ -2199,6 +2208,7 @@ package android.view.autofill { ctor public AutofillId(int, int); ctor public AutofillId(@NonNull android.view.autofill.AutofillId, long, int); method public boolean equalsIgnoreSession(@Nullable android.view.autofill.AutofillId); + method public boolean isNonVirtual(); method @NonNull public static android.view.autofill.AutofillId withoutSession(@NonNull android.view.autofill.AutofillId); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 55df824b1243..55b858e3c602 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5151,12 +5151,6 @@ public class Activity extends ContextThemeWrapper * #checkSelfPermission(String)}. * </p> * <p> - * Calling this API for permissions already granted to your app would show UI - * to the user to decide whether the app can still hold these permissions. This - * can be useful if the way your app uses data guarded by the permissions - * changes significantly. - * </p> - * <p> * You cannot request a permission if your activity sets {@link * android.R.styleable#AndroidManifestActivity_noHistory noHistory} to * <code>true</code> because in this case the activity would not receive diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 6e7bb83b2fcb..db838136867c 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -36,6 +36,7 @@ import android.os.WorkSource; 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 class ActivityManagerInternal { 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/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index fbc3b0d1753e..70fa4445479b 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -295,21 +295,6 @@ public class ActivityTaskManager { } /** - * Moves the top activity in the input rootTaskId to the pinned root task. - * @param rootTaskId Id of root task to move the top activity to pinned root task. - * @param bounds Bounds to use for pinned root task. - * @return True if the top activity of root task was successfully moved to the pinned root task. - */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) - public boolean moveTopActivityToPinnedRootTask(int rootTaskId, @NonNull Rect bounds) { - try { - return getService().moveTopActivityToPinnedRootTask(rootTaskId, bounds); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Start to enter lock task mode for given task by system(UI). * @param taskId Id of task to lock. */ diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 94b21187f0dd..1b8f04902842 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -508,7 +508,6 @@ interface IActivityManager { boolean stopBinderTrackingAndDump(in ParcelFileDescriptor fd); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) void suppressResizeConfigChanges(boolean suppress); - boolean moveTopActivityToPinnedRootTask(int rootTaskId, in Rect bounds); boolean isAppStartModeDisabled(int uid, in String packageName); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) boolean unlockUser(int userid, in byte[] token, in byte[] secret, diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 1d657114c6ff..b0853404a0aa 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -257,7 +257,6 @@ interface IActivityTaskManager { void keyguardGoingAway(int flags); void suppressResizeConfigChanges(boolean suppress); - boolean moveTopActivityToPinnedRootTask(int rootTaskId, in Rect bounds); /** * Resizes the docked stack, and all other stacks as the result of the dock stack bounds change. diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 697a377dd99f..bda2fa9b9622 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -228,4 +228,6 @@ interface INotificationManager NotificationListenerFilter getListenerFilter(in ComponentName cn, int userId); void setListenerFilter(in ComponentName cn, int userId, in NotificationListenerFilter nlf); + + void setToastRateLimitingEnabled(boolean enable); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8242e4db16c3..5fbc94822d4e 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -30,7 +30,6 @@ import android.annotation.IdRes; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.Px; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -83,6 +82,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; +import android.util.TypedValue; import android.util.proto.ProtoOutputStream; import android.view.ContextThemeWrapper; import android.view.Gravity; @@ -102,6 +102,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -218,6 +219,11 @@ public class Notification implements Parcelable private static final int MAX_REPLY_HISTORY = 5; /** + * Maximum aspect ratio of the large icon. 16:9 + */ + private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f; + + /** * Maximum number of (generic) action buttons in a notification (contextual action buttons are * handled separately). * @hide @@ -640,8 +646,14 @@ public class Notification implements Parcelable */ public static final int FLAG_IMMEDIATE_FGS_DISPLAY = 0x00002000; + private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList( + BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, + DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, + MessagingStyle.class); + /** @hide */ - @IntDef({FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE, + @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, + FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE, FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY, FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE, FLAG_IMMEDIATE_FGS_DISPLAY}) @@ -1523,6 +1535,14 @@ public class Notification implements Parcelable */ public static final int SEMANTIC_ACTION_CALL = 10; + /** + * {@code SemanticAction}: Mark the conversation associated with the notification as a + * priority. Note that this is only for use by the notification assistant services. + * @hide + */ + @SystemApi + public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; + private final Bundle mExtras; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private Icon mIcon; @@ -2233,7 +2253,8 @@ public class Notification implements Parcelable SEMANTIC_ACTION_UNMUTE, SEMANTIC_ACTION_THUMBS_UP, SEMANTIC_ACTION_THUMBS_DOWN, - SEMANTIC_ACTION_CALL + SEMANTIC_ACTION_CALL, + SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY }) @Retention(RetentionPolicy.SOURCE) public @interface SemanticAction {} @@ -4228,9 +4249,9 @@ public class Notification implements Parcelable /** * Add a large icon to the notification content view. * - * In the platform template, this image will be shown on the left of the notification view - * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small - * badge atop the large icon). + * In the platform template, this image will be shown either on the right of the + * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped) + * on the left in place of the {@link #setSmallIcon(Icon) small icon}. */ @NonNull public Builder setLargeIcon(Bitmap b) { @@ -4240,9 +4261,9 @@ public class Notification implements Parcelable /** * Add a large icon to the notification content view. * - * In the platform template, this image will be shown on the left of the notification view - * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small - * badge atop the large icon). + * In the platform template, this image will be shown either on the right of the + * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped) + * on the left in place of the {@link #setSmallIcon(Icon) small icon}. */ @NonNull public Builder setLargeIcon(Icon icon) { @@ -4911,7 +4932,8 @@ public class Notification implements Parcelable setTextViewColorPrimary(contentView, R.id.title, p); contentView.setViewLayoutWidth(R.id.title, showProgress ? ViewGroup.LayoutParams.WRAP_CONTENT - : ViewGroup.LayoutParams.MATCH_PARENT); + : ViewGroup.LayoutParams.MATCH_PARENT, + TypedValue.COMPLEX_UNIT_PX); } if (p.text != null && p.text.length() != 0) { int textId = showProgress ? com.android.internal.R.id.text_line_1 @@ -5083,7 +5105,7 @@ public class Notification implements Parcelable final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0); final int progress = ex.getInt(EXTRA_PROGRESS, 0); final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE); - if (p.hasProgress && (max != 0 || ind)) { + if (!p.mHideProgress && (max != 0 || ind)) { contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE); contentView.setProgressBar( R.id.progress, max, progress, ind); @@ -5109,8 +5131,7 @@ public class Notification implements Parcelable if (result == null) { result = new TemplateBindResult(); } - final boolean largeIconShown = bindLargeIcon(contentView, p); - calculateLargeIconMarginEnd(largeIconShown, result); + bindLargeIcon(contentView, p, result); if (p.mHeaderless) { // views in the headerless (collapsed) state result.mHeadingExtraMarginSet.applyToView(contentView, @@ -5122,28 +5143,54 @@ public class Notification implements Parcelable } } - private void calculateLargeIconMarginEnd(boolean largeIconShown, + // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, + // a use case that is not supported by the Compat Framework library. Workarounds to resolve + // the change's state in NotificationManagerService were very complex. These behavior + // changes are entirely visual, and should otherwise be undetectable by apps. + @SuppressWarnings("AndroidFrameworkCompatChange") + private void calculateLargeIconDimens(boolean largeIconShown, @NonNull TemplateBindResult result) { final Resources resources = mContext.getResources(); - final int contentMargin = resources.getDimensionPixelOffset( - R.dimen.notification_content_margin_end); - final int expanderSize = resources.getDimensionPixelSize( - R.dimen.notification_header_expand_icon_size) - contentMargin; - final int extraMarginEndIfVisible = resources.getDimensionPixelSize( - R.dimen.notification_right_icon_size) + contentMargin; - result.setRightIconState(largeIconShown, extraMarginEndIfVisible, expanderSize); + final float density = resources.getDisplayMetrics().density; + final float contentMarginDp = resources.getDimension( + R.dimen.notification_content_margin_end) / density; + final float expanderSizeDp = resources.getDimension( + R.dimen.notification_header_expand_icon_size) / density - contentMarginDp; + final float viewHeightDp = resources.getDimension( + R.dimen.notification_right_icon_size) / density; + float viewWidthDp = viewHeightDp; // icons are 1:1 by default + if (largeIconShown && ( + mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S + || DevFlags.shouldBackportSNotifRules(mContext.getContentResolver()))) { + Drawable drawable = mN.mLargeIcon.loadDrawable(mContext); + if (drawable != null) { + int iconWidth = drawable.getIntrinsicWidth(); + int iconHeight = drawable.getIntrinsicHeight(); + if (iconWidth > iconHeight && iconHeight > 0) { + final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO; + viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight, + maxViewWidthDp); + } + } + } + final float extraMarginEndDpIfVisible = viewWidthDp + contentMarginDp; + result.setRightIconState(largeIconShown, viewWidthDp, + extraMarginEndDpIfVisible, expanderSizeDp); } /** * Bind the large icon. - * @return if the largeIcon is visible */ - private boolean bindLargeIcon(RemoteViews contentView, StandardTemplateParams p) { + private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, + @NonNull TemplateBindResult result) { if (mN.mLargeIcon == null && mN.largeIcon != null) { mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon); } boolean showLargeIcon = mN.mLargeIcon != null && !p.hideLargeIcon; + calculateLargeIconDimens(showLargeIcon, result); if (showLargeIcon) { + contentView.setViewLayoutWidth(R.id.right_icon, + result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP); contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon); processLargeLegacyIcon(mN.mLargeIcon, contentView, p); @@ -5153,7 +5200,6 @@ public class Notification implements Parcelable // visibility) is used by NotificationGroupingUtil to set the visibility. contentView.setImageViewIcon(R.id.right_icon, null); } - return showLargeIcon; } private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) { @@ -5356,8 +5402,9 @@ public class Notification implements Parcelable final boolean snoozeEnabled = mContext.getContentResolver() != null && (Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1); - big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, - snoozeEnabled ? 0 : R.dimen.notification_content_margin); + int bottomMarginDimen = snoozeEnabled ? 0 : R.dimen.notification_content_margin; + big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, + RemoteViews.MARGIN_BOTTOM, bottomMarginDimen); } private static List<Notification.Action> filterOutContextualActions( @@ -5386,10 +5433,11 @@ public class Notification implements Parcelable int N = nonContextualActions.size(); boolean emphazisedMode = mN.fullScreenIntent != null; big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode); - if (N > 0) { + if (N > 0 && !p.mHideActions) { big.setViewVisibility(R.id.actions_container, View.VISIBLE); big.setViewVisibility(R.id.actions, View.VISIBLE); - big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0); + big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, + RemoteViews.MARGIN_BOTTOM, 0); if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS; for (int i=0; i<N; i++) { Action action = nonContextualActions.get(i); @@ -5478,6 +5526,71 @@ public class Notification implements Parcelable return createContentView(false /* increasedheight */ ); } + // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, + // a use case that is not supported by the Compat Framework library. Workarounds to resolve + // the change's state in NotificationManagerService were very complex. While it's possible + // apps can detect the change, it's most likely that the changes will simply result in + // visual regressions. + @SuppressWarnings("AndroidFrameworkCompatChange") + private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) { + // Custom views which come from a platform style class are safe, and thus do not need to + // be wrapped. Any subclass of those styles has the opportunity to make arbitrary + // changes to the RemoteViews, and thus can't be trusted as a fully vetted view. + if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) { + return false; + } + final ContentResolver contentResolver = mContext.getContentResolver(); + final int decorationType = DevFlags.getFullyCustomViewNotifDecoration(contentResolver); + return decorationType != DevFlags.DECORATION_NONE + && (DevFlags.shouldBackportSNotifRules(contentResolver) + || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S); + } + + private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) { + int decorationType = + DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver()); + StandardTemplateParams p = mParams.reset() + .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) + .decorationType(decorationType) + .fillTextsFrom(this); + TemplateBindResult result = new TemplateBindResult(); + RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result); + buildCustomContentIntoTemplate(mContext, standard, customContent, + p, result, decorationType); + return standard; + } + + private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) { + int decorationType = + DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver()); + StandardTemplateParams p = mParams.reset() + .viewType(StandardTemplateParams.VIEW_TYPE_BIG) + .decorationType(decorationType) + .fillTextsFrom(this); + TemplateBindResult result = new TemplateBindResult(); + RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(), + p, result); + buildCustomContentIntoTemplate(mContext, standard, customContent, + p, result, decorationType); + return standard; + } + + private RemoteViews minimallyDecoratedHeadsUpContentView( + @NonNull RemoteViews customContent) { + int decorationType = + DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver()); + StandardTemplateParams p = mParams.reset() + .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) + .decorationType(decorationType) + .fillTextsFrom(this); + TemplateBindResult result = new TemplateBindResult(); + RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), + p, result); + buildCustomContentIntoTemplate(mContext, standard, customContent, + p, result, decorationType); + return standard; + } + /** * Construct a RemoteViews for the smaller content view. * @@ -5490,11 +5603,13 @@ public class Notification implements Parcelable */ public RemoteViews createContentView(boolean increasedHeight) { if (mN.contentView != null && useExistingRemoteView()) { - return mN.contentView; + return fullyCustomViewRequiresDecoration(false /* fromStyle */) + ? minimallyDecoratedContentView(mN.contentView) : mN.contentView; } else if (mStyle != null) { final RemoteViews styleView = mStyle.makeContentView(increasedHeight); if (styleView != null) { - return styleView; + return fullyCustomViewRequiresDecoration(true /* fromStyle */) + ? minimallyDecoratedContentView(styleView) : styleView; } } StandardTemplateParams p = mParams.reset() @@ -5514,18 +5629,30 @@ public class Notification implements Parcelable public RemoteViews createBigContentView() { RemoteViews result = null; if (mN.bigContentView != null && useExistingRemoteView()) { - return mN.bigContentView; + return fullyCustomViewRequiresDecoration(false /* fromStyle */) + ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView; } if (mStyle != null) { result = mStyle.makeBigContentView(); hideLine1Text(result); + if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) { + result = minimallyDecoratedBigContentView(result); + } } - if (result == null && bigContentViewRequired()) { - StandardTemplateParams p = mParams.reset() - .viewType(StandardTemplateParams.VIEW_TYPE_BIG) - .fillTextsFrom(this); - result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p, - null /* result */); + if (result == null) { + if (bigContentViewRequired()) { + StandardTemplateParams p = mParams.reset() + .viewType(StandardTemplateParams.VIEW_TYPE_BIG) + .fillTextsFrom(this); + result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p, + null /* result */); + } else if (DevFlags.shouldBackportSNotifRules(mContext.getContentResolver()) + && useExistingRemoteView() + && fullyCustomViewRequiresDecoration(false /* fromStyle */)) { + // This "backport" logic is a special case to handle the UNDO style of notif + // so that we can see what that will look like when the app targets S. + result = minimallyDecoratedBigContentView(mN.contentView); + } } makeHeaderExpanded(result); return result; @@ -5619,11 +5746,14 @@ public class Notification implements Parcelable */ public RemoteViews createHeadsUpContentView(boolean increasedHeight) { if (mN.headsUpContentView != null && useExistingRemoteView()) { - return mN.headsUpContentView; + return fullyCustomViewRequiresDecoration(false /* fromStyle */) + ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView) + : mN.headsUpContentView; } else if (mStyle != null) { final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight); if (styleView != null) { - return styleView; + return fullyCustomViewRequiresDecoration(true /* fromStyle */) + ? minimallyDecoratedHeadsUpContentView(styleView) : styleView; } } else if (mActions.size() == 0) { return null; @@ -5635,8 +5765,7 @@ public class Notification implements Parcelable .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) .fillTextsFrom(this) .setMaxRemoteInputHistory(1); - return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), - p, + return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p, null /* result */); } @@ -6554,11 +6683,7 @@ public class Notification implements Parcelable */ @SystemApi public static Class<? extends Style> getNotificationStyleClass(String templateClass) { - Class<? extends Style>[] classes = new Class[] { - BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, - DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, - MessagingStyle.class }; - for (Class<? extends Style> innerClass : classes) { + for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) { if (templateClass.equals(innerClass.getName())) { return innerClass; } @@ -6566,6 +6691,56 @@ public class Notification implements Parcelable return null; } + private static void buildCustomContentIntoTemplate(@NonNull Context context, + @NonNull RemoteViews template, @Nullable RemoteViews customContent, + @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result, + int decorationType) { + int childIndex = -1; + if (customContent != null) { + // Need to clone customContent before adding, because otherwise it can no longer be + // parceled independently of remoteViews. + customContent = customContent.clone(); + if (p.mHeaderless) { + if (decorationType <= DevFlags.DECORATION_PARTIAL) { + template.removeFromParent(R.id.notification_top_line); + } + if (decorationType != DevFlags.DECORATION_FULL_COMPATIBLE) { + // Change the max content size from 60dp (the compatible size) to 48dp + // (the constrained size). This is done by increasing the minimum margin + // (implemented as top/bottom margins) and decreasing the extra margin + // (implemented as the height of shrinkable top/bottom views in the column). + template.setViewLayoutMarginDimen( + R.id.notification_headerless_view_column, + RemoteViews.MARGIN_TOP, + R.dimen.notification_headerless_margin_constrained_minimum); + template.setViewLayoutMarginDimen( + R.id.notification_headerless_view_column, + RemoteViews.MARGIN_BOTTOM, + R.dimen.notification_headerless_margin_constrained_minimum); + template.setViewLayoutHeightDimen( + R.id.notification_headerless_margin_extra_top, + R.dimen.notification_headerless_margin_constrained_extra); + template.setViewLayoutHeightDimen( + R.id.notification_headerless_margin_extra_bottom, + R.dimen.notification_headerless_margin_constrained_extra); + } + } else { + // also update the end margin to account for the large icon or expander + Resources resources = context.getResources(); + result.mTitleMarginSet.applyToView(template, R.id.notification_main_column, + resources.getDimension(R.dimen.notification_content_margin_end) + / resources.getDisplayMetrics().density); + } + template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress); + template.addView(R.id.notification_main_column, customContent, 0 /* index */); + template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); + childIndex = 0; + } + template.setIntTag(R.id.notification_main_column, + com.android.internal.R.id.notification_custom_view_index_tag, + childIndex); + } + /** * An object that can apply a rich notification style to a {@link Notification.Builder} * object. @@ -7771,7 +7946,7 @@ public class Notification implements Parcelable StandardTemplateParams p = mBuilder.mParams.reset() .viewType(isCollapsed ? StandardTemplateParams.VIEW_TYPE_NORMAL : StandardTemplateParams.VIEW_TYPE_BIG) - .hasProgress(false) + .hideProgress(true) .title(conversationTitle) .text(null) .hideLargeIcon(hideRightIcons || isOneToOne) @@ -7788,8 +7963,9 @@ public class Notification implements Parcelable // also update the end margin if there is an image // NOTE: This template doesn't support moving this icon to the left, so we don't // need to fully apply the MarginSet - contentView.setViewLayoutMarginEnd(R.id.notification_messaging, - bindResult.mHeadingExtraMarginSet.getValue()); + contentView.setViewLayoutMargin(R.id.notification_messaging, RemoteViews.MARGIN_END, + bindResult.mHeadingExtraMarginSet.getDpValue(), + TypedValue.COMPLEX_UNIT_DIP); } contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", mBuilder.isColorized(p) @@ -8585,7 +8761,8 @@ public class Notification implements Parcelable private RemoteViews makeMediaContentView() { StandardTemplateParams p = mBuilder.mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) - .hasProgress(false).fillTextsFrom(mBuilder); + .hideProgress(true) + .fillTextsFrom(mBuilder); RemoteViews view = mBuilder.applyStandardTemplate( R.layout.notification_template_material_media, p, null /* result */); @@ -8613,7 +8790,8 @@ public class Notification implements Parcelable if (mBuilder.mN.hasLargeIcon()) { endMargin = R.dimen.notification_media_image_margin_end; } - view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); + view.setViewLayoutMarginDimen(R.id.notification_main_column, + RemoteViews.MARGIN_END, endMargin); return view; } @@ -8633,7 +8811,8 @@ public class Notification implements Parcelable } StandardTemplateParams p = mBuilder.mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_BIG) - .hasProgress(false).fillTextsFrom(mBuilder); + .hideProgress(true) + .fillTextsFrom(mBuilder); RemoteViews big = mBuilder.applyStandardTemplate( R.layout.notification_template_material_big_media, p , null /* result */); @@ -8650,8 +8829,8 @@ public class Notification implements Parcelable private void handleImage(RemoteViews contentView) { if (mBuilder.mN.hasLargeIcon()) { - contentView.setViewLayoutMarginEndDimen(R.id.line1, 0); - contentView.setViewLayoutMarginEndDimen(R.id.text, 0); + contentView.setViewLayoutMarginDimen(R.id.line1, RemoteViews.MARGIN_END, 0); + contentView.setViewLayoutMarginDimen(R.id.text, RemoteViews.MARGIN_END, 0); } } @@ -8727,29 +8906,39 @@ public class Notification implements Parcelable RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null ? mBuilder.mN.contentView : mBuilder.mN.headsUpContentView; + if (headsUpContentView == null) { + return null; // no custom view; use the default behavior + } if (mBuilder.mActions.size() == 0) { return makeStandardTemplateWithCustomContent(headsUpContentView); } + int decorationType = getDecorationType(); TemplateBindResult result = new TemplateBindResult(); StandardTemplateParams p = mBuilder.mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) - .hasCustomContent(headsUpContentView != null) + .decorationType(decorationType) .fillTextsFrom(mBuilder); RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( mBuilder.getHeadsUpBaseLayoutResource(), p, result); - buildIntoRemoteViewContent(remoteViews, headsUpContentView, result, true); + buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView, + p, result, decorationType); return remoteViews; } private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) { + if (customContent == null) { + return null; // no custom view; use the default behavior + } + int decorationType = getDecorationType(); TemplateBindResult result = new TemplateBindResult(); StandardTemplateParams p = mBuilder.mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) - .hasCustomContent(customContent != null) + .decorationType(decorationType) .fillTextsFrom(mBuilder); RemoteViews remoteViews = mBuilder.applyStandardTemplate( mBuilder.getBaseLayoutResource(), p, result); - buildIntoRemoteViewContent(remoteViews, customContent, result, true); + buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent, + p, result, decorationType); return remoteViews; } @@ -8757,37 +8946,37 @@ public class Notification implements Parcelable RemoteViews bigContentView = mBuilder.mN.bigContentView == null ? mBuilder.mN.contentView : mBuilder.mN.bigContentView; + if (bigContentView == null) { + return null; // no custom view; use the default behavior + } + int decorationType = getDecorationType(); TemplateBindResult result = new TemplateBindResult(); StandardTemplateParams p = mBuilder.mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_BIG) - .hasCustomContent(bigContentView != null) + .decorationType(decorationType) .fillTextsFrom(mBuilder); RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( mBuilder.getBigBaseLayoutResource(), p, result); - buildIntoRemoteViewContent(remoteViews, bigContentView, result, false); + buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView, + p, result, decorationType); return remoteViews; } - private void buildIntoRemoteViewContent(RemoteViews remoteViews, - RemoteViews customContent, TemplateBindResult result, boolean headerless) { - int childIndex = -1; - if (customContent != null) { - // Need to clone customContent before adding, because otherwise it can no longer be - // parceled independently of remoteViews. - customContent = customContent.clone(); - remoteViews.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress); - remoteViews.addView(R.id.notification_main_column, customContent, 0 /* index */); - remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); - childIndex = 0; - } - remoteViews.setIntTag(R.id.notification_main_column, - com.android.internal.R.id.notification_custom_view_index_tag, - childIndex); - if (!headerless) { - // also update the end margin to account for the large icon or expander - Resources resources = mBuilder.mContext.getResources(); - result.mTitleMarginSet.applyToView(remoteViews, R.id.notification_main_column, - resources.getDimensionPixelOffset(R.dimen.notification_content_margin_end)); + // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, + // a use case that is not supported by the Compat Framework library. Workarounds to resolve + // the change's state in NotificationManagerService were very complex. While it's possible + // apps can detect the change, it's most likely that the changes will simply result in + // visual regressions. + @SuppressWarnings("AndroidFrameworkCompatChange") + private int getDecorationType() { + ContentResolver contentResolver = mBuilder.mContext.getContentResolver(); + if (mBuilder.mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S + || DevFlags.shouldBackportSNotifRules(contentResolver)) { + return DevFlags.getDecoratedCustomViewNotifDecoration(contentResolver); + } else { + // For apps that don't target S, this decoration provides the closest behavior to R, + // but doesn't fit with the design guidelines for S. + return DevFlags.DECORATION_FULL_COMPATIBLE; } } @@ -11025,6 +11214,7 @@ public class Notification implements Parcelable */ private static class TemplateBindResult { boolean mRightIconVisible; + float mRightIconWidthDp; /** * The margin end that needs to be added to the heading so that it won't overlap @@ -11049,11 +11239,13 @@ public class Notification implements Parcelable */ public final MarginSet mTitleMarginSet = new MarginSet(); - public void setRightIconState(boolean visible, int marginEndIfVisible, int expanderSize) { + public void setRightIconState(boolean visible, float widthDp, + float marginEndDpIfVisible, float expanderSizeDp) { mRightIconVisible = visible; - mHeadingExtraMarginSet.setValues(0, marginEndIfVisible); - mHeadingFullMarginSet.setValues(expanderSize, marginEndIfVisible + expanderSize); - mTitleMarginSet.setValues(0, marginEndIfVisible + expanderSize); + mRightIconWidthDp = widthDp; + mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible); + mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp); + mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp); } /** @@ -11062,10 +11254,10 @@ public class Notification implements Parcelable * left_icon and adjust the margins, and to undo that change as well. */ private class MarginSet { - private int mValueIfGone; - private int mValueIfVisible; + private float mValueIfGone; + private float mValueIfVisible; - public void setValues(int valueIfGone, int valueIfVisible) { + public void setValues(float valueIfGone, float valueIfVisible) { mValueIfGone = valueIfGone; mValueIfVisible = valueIfVisible; } @@ -11075,22 +11267,26 @@ public class Notification implements Parcelable } public void applyToView(@NonNull RemoteViews views, @IdRes int viewId, - @Px int extraMargin) { - final int marginEnd = getValue() + extraMargin; + float extraMarginDp) { + final float marginEndDp = getDpValue() + extraMarginDp; if (viewId == R.id.notification_header) { - views.setInt(R.id.notification_header, "setTopLineExtraMarginEnd", marginEnd); + views.setFloat(R.id.notification_header, + "setTopLineExtraMarginEndDp", marginEndDp); } else { - views.setViewLayoutMarginEnd(viewId, marginEnd); + views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, + marginEndDp, TypedValue.COMPLEX_UNIT_DIP); } if (mRightIconVisible) { views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible, - mValueIfVisible + extraMargin); + TypedValue.createComplexDimension( + mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP)); views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone, - mValueIfGone + extraMargin); + TypedValue.createComplexDimension( + mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP)); } } - public int getValue() { + public float getDpValue() { return mRightIconVisible ? mValueIfVisible : mValueIfGone; } } @@ -11107,8 +11303,9 @@ public class Notification implements Parcelable int mViewType = VIEW_TYPE_UNSPECIFIED; boolean mHeaderless; - boolean mHasCustomContent; - boolean hasProgress = true; + boolean mHideTitle; + boolean mHideActions; + boolean mHideProgress; CharSequence title; CharSequence text; CharSequence headerTextSecondary; @@ -11121,8 +11318,9 @@ public class Notification implements Parcelable final StandardTemplateParams reset() { mViewType = VIEW_TYPE_UNSPECIFIED; mHeaderless = false; - mHasCustomContent = false; - hasProgress = true; + mHideTitle = false; + mHideActions = false; + mHideProgress = false; title = null; text = null; summaryText = null; @@ -11134,9 +11332,7 @@ public class Notification implements Parcelable } final boolean hasTitle() { - // We hide the title when the notification is a decorated custom view so that decorated - // custom views always have to include their own title. - return !TextUtils.isEmpty(title) && !mHasCustomContent; + return !TextUtils.isEmpty(title) && !mHideTitle; } final StandardTemplateParams viewType(int viewType) { @@ -11149,13 +11345,18 @@ public class Notification implements Parcelable return this; } - final StandardTemplateParams hasProgress(boolean hasProgress) { - this.hasProgress = hasProgress; + final StandardTemplateParams hideActions(boolean hideActions) { + this.mHideActions = hideActions; return this; } - final StandardTemplateParams hasCustomContent(boolean hasCustomContent) { - this.mHasCustomContent = hasCustomContent; + final StandardTemplateParams hideProgress(boolean hideProgress) { + this.mHideProgress = hideProgress; + return this; + } + + final StandardTemplateParams hideTitle(boolean hideTitle) { + this.mHideTitle = hideTitle; return this; } @@ -11211,6 +11412,16 @@ public class Notification implements Parcelable this.maxRemoteInputHistory = maxRemoteInputHistory; return this; } + + public StandardTemplateParams decorationType(int decorationType) { + boolean hideTitle = decorationType <= DevFlags.DECORATION_FULL_COMPATIBLE; + boolean hideOtherFields = decorationType <= DevFlags.DECORATION_MINIMAL; + hideTitle(hideTitle); + hideLargeIcon(hideOtherFields); + hideProgress(hideOtherFields); + hideActions(hideOtherFields); + return this; + } } /** @@ -11220,7 +11431,67 @@ public class Notification implements Parcelable * @hide */ public static class DevFlags { + + /** + * Notifications will not be decorated. The custom content will be shown as-is. + * + * <p>NOTE: This option is not available for notifications with DecoratedCustomViewStyle, + * as that API contract includes decorations that this does not provide. + */ + public static final int DECORATION_NONE = 0; + + /** + * Notifications will be minimally decorated with ONLY an icon and expander as follows: + * <li>A large icon is never shown. + * <li>A progress bar is never shown. + * <li>The expanded and heads up states do not show actions, even if provided. + * <li>The collapsed state gives the app's custom content 48dp of vertical space. + * <li>The collapsed state does NOT include the top line of views, + * like the alerted icon or work profile badge. + * + * <p>NOTE: This option is not available for notifications with DecoratedCustomViewStyle, + * as that API contract includes decorations that this does not provide. + */ + public static final int DECORATION_MINIMAL = 1; + + /** + * Notifications will be partially decorated with AT LEAST an icon and expander as follows: + * <li>A large icon is shown if provided. + * <li>A progress bar is shown if provided and enough space remains below the content. + * <li>Actions are shown in the expanded and heads up states. + * <li>The collapsed state gives the app's custom content 48dp of vertical space. + * <li>The collapsed state does NOT include the top line of views, + * like the alerted icon or work profile badge. + */ + public static final int DECORATION_PARTIAL = 2; + + /** + * Notifications will be fully decorated as follows: + * <li>A large icon is shown if provided. + * <li>A progress bar is shown if provided and enough space remains below the content. + * <li>Actions are shown in the expanded and heads up states. + * <li>The collapsed state gives the app's custom content 40dp of vertical space. + * <li>The collapsed state DOES include the top line of views, + * like the alerted icon or work profile badge. + * <li>The collapsed state's top line views will never show the title text. + */ + public static final int DECORATION_FULL_COMPATIBLE = 3; + + /** + * Notifications will be fully decorated as follows: + * <li>A large icon is shown if provided. + * <li>A progress bar is shown if provided and enough space remains below the content. + * <li>Actions are shown in the expanded and heads up states. + * <li>The collapsed state gives the app's custom content 20dp of vertical space. + * <li>The collapsed state DOES include the top line of views + * like the alerted icon or work profile badge. + * <li>The collapsed state's top line views will contain the title text if provided. + */ + public static final int DECORATION_FULL_CONSTRAINED = 4; + private static final boolean DEFAULT_BACKPORT_S_NOTIF_RULES = false; + private static final int DEFAULT_FULLY_CUSTOM_DECORATION = DECORATION_MINIMAL; + private static final int DEFAULT_DECORATED_DECORATION = DECORATION_PARTIAL; /** * Used by unit tests to force that this class returns its default values, which is required @@ -11240,5 +11511,37 @@ public class Notification implements Parcelable return Settings.Global.getInt(contentResolver, Settings.Global.BACKPORT_S_NOTIF_RULES, DEFAULT_BACKPORT_S_NOTIF_RULES ? 1 : 0) == 1; } + + /** + * @return the decoration type to be applied to notifications with fully custom view. + * @hide + */ + public static int getFullyCustomViewNotifDecoration( + @NonNull ContentResolver contentResolver) { + if (sForceDefaults) { + return DEFAULT_FULLY_CUSTOM_DECORATION; + } + final int decoration = Settings.Global.getInt(contentResolver, + Settings.Global.FULLY_CUSTOM_VIEW_NOTIF_DECORATION, + DEFAULT_FULLY_CUSTOM_DECORATION); + // clamp to a valid decoration value + return Math.max(DECORATION_NONE, Math.min(decoration, DECORATION_FULL_CONSTRAINED)); + } + + /** + * @return the decoration type to be applied to notifications with DecoratedCustomViewStyle. + * @hide + */ + public static int getDecoratedCustomViewNotifDecoration( + @NonNull ContentResolver contentResolver) { + if (sForceDefaults) { + return DEFAULT_DECORATED_DECORATION; + } + final int decoration = Settings.Global.getInt(contentResolver, + Settings.Global.DECORATED_CUSTOM_VIEW_NOTIF_DECORATION, + DEFAULT_DECORATED_DECORATION); + // clamp to a valid decoration value (and don't allow decoration to be NONE or MINIMAL) + return Math.max(DECORATION_PARTIAL, Math.min(decoration, DECORATION_FULL_CONSTRAINED)); + } } } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 58f382dc2c6c..6cce270bc5a3 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1730,6 +1730,23 @@ public class NotificationManager { } /** + * Controls whether toast rate limiting is enabled for the calling uid. + * + * @param enable true to enable toast rate limiting, false to disable it + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) + public void setToastRateLimitingEnabled(boolean enable) { + INotificationManager service = getService(); + try { + service.setToastRateLimitingEnabled(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Notification policy configuration. Represents user-preferences for notification * filtering. */ diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 06ad9c99c301..c804e6427dce 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 diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 9dbf1ff6f7c9..602e9a338feb 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -19,6 +19,7 @@ package android.app; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; @@ -221,6 +222,7 @@ public final class PendingIntent implements Parcelable { * @hide */ @Deprecated + @TestApi public static final int FLAG_MUTABLE_UNAUDITED = FLAG_MUTABLE; /** diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index ae1c89468b18..050d194c4094 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -29,6 +29,7 @@ import android.app.blob.BlobStoreManagerFrameworkInitializer; import android.app.contentsuggestions.ContentSuggestionsManager; import android.app.contentsuggestions.IContentSuggestionsManager; import android.app.job.JobSchedulerFrameworkInitializer; +import android.app.people.PeopleManager; import android.app.prediction.AppPredictionManager; import android.app.role.RoleControllerManager; import android.app.role.RoleManager; @@ -586,6 +587,13 @@ public final class SystemServiceRegistry { return new NsdManager(ctx.getOuterContext(), service); }}); + registerService(Context.PEOPLE_SERVICE, PeopleManager.class, + new CachedServiceFetcher<PeopleManager>() { + @Override + public PeopleManager createService(ContextImpl ctx) throws ServiceNotFoundException { + return new PeopleManager(ctx); + }}); + registerService(Context.POWER_SERVICE, PowerManager.class, new CachedServiceFetcher<PowerManager>() { @Override diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 61c4d39c10b0..623c878d7bfa 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -36,6 +36,7 @@ import android.window.TaskSnapshot; import android.window.WindowContainerToken; import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -279,6 +280,24 @@ public class TaskInfo { launchCookies.add(cookie); } + /** + * @return {@code true} if this task contains the launch cookie. + * @hide + */ + @TestApi + public boolean containsLaunchCookie(@NonNull IBinder cookie) { + return launchCookies.contains(cookie); + } + + /** + * @return The parent task id of this task. + * @hide + */ + @TestApi + public int getParentTaskId() { + return parentTaskId; + } + /** @hide */ @TestApi public boolean hasParentTask() { diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 787393ed0f6c..05872bad30f0 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -438,8 +438,8 @@ public final class UiAutomation { * Adopt the permission identity of the shell UID for all permissions. This allows * you to call APIs protected permissions which normal apps cannot hold but are * granted to the shell UID. If you already adopted all shell permissions by calling - * this method or {@link #adoptShellPermissionIdentity(String...)} a subsequent call - * would be a no-op. Note that your permission state becomes that of the shell UID + * this method or {@link #adoptShellPermissionIdentity(String...)} a subsequent call will + * replace any previous adoption. Note that your permission state becomes that of the shell UID * and it is not a combination of your and the shell UID permissions. * <p> * <strong>Note:<strong/> Calling this method adopts all shell permissions and overrides @@ -460,13 +460,13 @@ public final class UiAutomation { /** * Adopt the permission identity of the shell UID only for the provided permissions. * This allows you to call APIs protected permissions which normal apps cannot hold - * but are granted to the shell UID. If you already adopted the specified shell - * permissions by calling this method or {@link #adoptShellPermissionIdentity()} a - * subsequent call would be a no-op. Note that your permission state becomes that of the - * shell UID and it is not a combination of your and the shell UID permissions. + * but are granted to the shell UID. If you already adopted shell permissions by calling + * this method, or {@link #adoptShellPermissionIdentity()} a subsequent call will replace any + * previous adoption. * <p> - * <strong>Note:<strong/> Calling this method adopts only the specified shell permissions - * and overrides all adopted permissions via {@link #adoptShellPermissionIdentity()}. + * <strong>Note:<strong/> This method behave differently from + * {@link #adoptShellPermissionIdentity()}. Only the listed permissions will use the shell + * identity and other permissions will still check against the original UID * * @param permissions The permissions to adopt or <code>null</code> to adopt all. * diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 259c1a16d85a..1789b635a6fc 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -39,7 +39,6 @@ import android.annotation.WorkerThread; 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; @@ -306,7 +305,13 @@ public class DevicePolicyManager { * of the provisioning flow was successful, although this doesn't guarantee the full flow will * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies * that the user backed-out of provisioning, or some precondition for provisioning wasn't met. + * + * @deprecated admin apps must now implement activities with intent filters for the {@link + * #ACTION_GET_PROVISIONING_MODE} and {@link #ACTION_ADMIN_POLICY_COMPLIANCE} intent actions; + * using {@link #ACTION_PROVISION_MANAGED_DEVICE} to start provisioning will cause the + * provisioning to fail. */ + @Deprecated @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_PROVISION_MANAGED_DEVICE = "android.app.action.PROVISION_MANAGED_DEVICE"; @@ -598,6 +603,12 @@ public class DevicePolicyManager { * properties will be converted into a {@link android.os.PersistableBundle} and passed to the * management application after provisioning. * + * <p>Admin apps will receive this extra in their {@link #ACTION_GET_PROVISIONING_MODE} and + * {@link #ACTION_ADMIN_POLICY_COMPLIANCE} intent handlers. Additionally, {@link + * #ACTION_GET_PROVISIONING_MODE} may also return this extra which will then be sent over to + * {@link #ACTION_ADMIN_POLICY_COMPLIANCE}, alongside the original values that were passed to + * {@link #ACTION_GET_PROVISIONING_MODE}. + * * <p> * In both cases the application receives the data in * {@link DeviceAdminReceiver#onProfileProvisioningComplete} via an intent with the action @@ -655,7 +666,9 @@ public class DevicePolicyManager { * profile provisioning. If the account supplied is present in the primary user, it will be * copied, along with its credentials to the managed profile and removed from the primary user. * - * Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}. + * Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}, with managed account provisioning, or + * return as an extra to the intent result from the {@link #ACTION_GET_PROVISIONING_MODE} + * activity. */ public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE @@ -669,8 +682,10 @@ public class DevicePolicyManager { * * <p> Defaults to {@code false} * - * <p> Use with {@link #ACTION_PROVISION_MANAGED_PROFILE} and - * {@link #EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE} + * <p> Use with {@link #ACTION_PROVISION_MANAGED_PROFILE} or set as an extra to the + * intent result of the {@link #ACTION_GET_PROVISIONING_MODE} activity. + * + * @see #EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE */ public static final String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION"; @@ -697,8 +712,9 @@ public class DevicePolicyManager { * A Boolean extra that can be used by the mobile device management application to skip the * disabling of system apps during provisioning when set to {@code true}. * - * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} or an intent with action - * {@link #ACTION_PROVISION_MANAGED_DEVICE} that starts device owner provisioning. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC}, an intent with action + * {@link #ACTION_PROVISION_MANAGED_PROFILE} that starts profile owner provisioning or + * set as an extra to the intent result of the {@link #ACTION_GET_PROVISIONING_MODE} activity. */ public static final String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED"; @@ -1175,27 +1191,15 @@ public class DevicePolicyManager { "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT"; /** - * A boolean extra indicating if user setup should be skipped, for when provisioning is started - * during setup-wizard. - * - * <p>If unspecified, defaults to {@code true} to match the behavior in - * {@link android.os.Build.VERSION_CODES#M} and earlier. - * - * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE} or - * {@link #ACTION_PROVISION_MANAGED_USER}. - * - * @hide - */ - public static final String EXTRA_PROVISIONING_SKIP_USER_SETUP = - "android.app.extra.PROVISIONING_SKIP_USER_SETUP"; - - /** * A boolean extra indicating if the user consent steps from the provisioning flow should be * skipped. If unspecified, defaults to {@code false}. * * It can only be used by an existing device owner trying to create a managed profile via * {@link #ACTION_PROVISION_MANAGED_PROFILE}. Otherwise it is ignored. + * + * @deprecated this extra is no longer relevant as device owners cannot create managed profiles */ + @Deprecated public static final String EXTRA_PROVISIONING_SKIP_USER_CONSENT = "android.app.extra.PROVISIONING_SKIP_USER_CONSENT"; @@ -1252,7 +1256,8 @@ public class DevicePolicyManager { @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 {} @@ -1379,6 +1384,15 @@ public class DevicePolicyManager { 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. @@ -2456,6 +2470,16 @@ public class DevicePolicyManager { * <p>The target activity may also return the account that needs to be migrated from primary * user to managed profile in case of a profile owner provisioning in * {@link #EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE} as result. + * + * <p>The target activity may also include the {@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE} + * extra in the intent result. The values of this {@link android.os.PersistableBundle} will be + * sent as an intent extra of the same name to the {@link #ACTION_ADMIN_POLICY_COMPLIANCE} + * activity, along with the values of the {@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE} extra + * that are already supplied to this activity. + * + * @see #EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION + * @see #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED + * @see #ACTION_ADMIN_POLICY_COMPLIANCE */ public static final String ACTION_GET_PROVISIONING_MODE = "android.app.action.GET_PROVISIONING_MODE"; @@ -2504,6 +2528,7 @@ public class DevicePolicyManager { * @see #SUPPORTED_MODES_ORGANIZATION_OWNED * @see #SUPPORTED_MODES_PERSONALLY_OWNED * @see #SUPPORTED_MODES_ORGANIZATION_AND_PERSONALLY_OWNED + * @see #SUPPORTED_MODES_DEVICE_OWNER * @hide */ @SystemApi @@ -2545,6 +2570,11 @@ public class DevicePolicyManager { * provisioning flow (in which case this gets sent after {@link #ACTION_GET_PROVISIONING_MODE}), * or it could happen during provisioning finalization if the administrator supports * finalization during setup wizard. + * + * <p>Intents with this action may also be supplied with the {@link + * #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE} extra. + * + * @see #ACTION_GET_PROVISIONING_MODE */ public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE"; @@ -2990,6 +3020,10 @@ public class DevicePolicyManager { * * <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 @@ -3009,6 +3043,9 @@ public class DevicePolicyManager { * 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) { @@ -4025,10 +4062,18 @@ public class DevicePolicyManager { * <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) { @@ -11241,6 +11286,7 @@ public class DevicePolicyManager { * {@code android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS} or the caller is * not {@link UserHandle#SYSTEM_USER} */ + @TestApi public void forceUpdateUserSetupComplete() { try { mService.forceUpdateUserSetupComplete(); @@ -12782,4 +12828,66 @@ public class DevicePolicyManager { } } } + + /** + * 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/DevicePolicySafetyChecker.java b/core/java/android/app/admin/DevicePolicySafetyChecker.java index 1f8a9335b9ac..b1a80c55cf1e 100644 --- a/core/java/android/app/admin/DevicePolicySafetyChecker.java +++ b/core/java/android/app/admin/DevicePolicySafetyChecker.java @@ -18,6 +18,8 @@ package android.app.admin; import android.annotation.NonNull; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; +import com.android.internal.os.IResultReceiver; + /** * Interface responsible to check if a {@link DevicePolicyManager} API can be safely executed. * @@ -28,9 +30,7 @@ public interface DevicePolicySafetyChecker { /** * Returns whether the given {@code operation} can be safely executed at the moment. */ - default boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation) { - return true; - } + boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation); /** * Returns a new exception for when the given {@code operation} cannot be safely executed. @@ -39,4 +39,13 @@ public interface DevicePolicySafetyChecker { default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation) { return new UnsafeStateException(operation); } + + /** + * Called when a request was made to factory reset the device, so it can be delayed if it's not + * safe to proceed. + * + * @param callback callback whose {@code send()} method must be called when it's safe to factory + * reset. + */ + void onFactoryReset(IResultReceiver callback); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 4b87bb9cae6c..e81abfe5a409 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -489,4 +489,7 @@ interface IDevicePolicyManager { 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/app/assist/AssistStructure.aidl b/core/java/android/app/assist/AssistStructure.aidl index ae0a34c92a0e..b997bbbe7d7c 100644 --- a/core/java/android/app/assist/AssistStructure.aidl +++ b/core/java/android/app/assist/AssistStructure.aidl @@ -17,3 +17,8 @@ package android.app.assist; parcelable AssistStructure; + +/** + * {@hide} + */ +parcelable AssistStructure.ViewNodeParcelable; diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index c15504cc0843..65a21640bb98 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -271,7 +271,8 @@ public class AssistStructure implements Parcelable { + ", views=" + mNumWrittenViews + ", level=" + (mCurViewStackPos+levelAdj)); out.writeInt(VALIDATE_VIEW_TOKEN); - int flags = child.writeSelfToParcel(out, pwriter, mSanitizeOnWrite, mTmpMatrix); + int flags = child.writeSelfToParcel(out, pwriter, mSanitizeOnWrite, + mTmpMatrix, /*willWriteChildren=*/true); mNumWrittenViews++; // If the child has children, push it on the stack to write them next. if ((flags&ViewNode.FLAGS_HAS_CHILDREN) != 0) { @@ -671,6 +672,7 @@ public class AssistStructure implements Parcelable { static final int FLAGS_CONTEXT_CLICKABLE = 0x00004000; static final int FLAGS_OPAQUE = 0x00008000; + static final int FLAGS_HAS_MIME_TYPES = 0x80000000; static final int FLAGS_HAS_MATRIX = 0x40000000; static final int FLAGS_HAS_ALPHA = 0x20000000; static final int FLAGS_HAS_ELEVATION = 0x10000000; @@ -714,6 +716,7 @@ public class AssistStructure implements Parcelable { String mWebDomain; Bundle mExtras; LocaleList mLocaleList; + String[] mOnReceiveContentMimeTypes; ViewNode[] mChildren; @@ -724,11 +727,51 @@ public class AssistStructure implements Parcelable { public ViewNode() { } + ViewNode(@NonNull Parcel in) { + initializeFromParcelWithoutChildren(in, /*preader=*/null, /*tmpMatrix=*/null); + } + ViewNode(ParcelTransferReader reader, int nestingLevel) { final Parcel in = reader.readParcel(VALIDATE_VIEW_TOKEN, nestingLevel); reader.mNumReadViews++; - final PooledStringReader preader = reader.mStringReader; - mClassName = preader.readString(); + initializeFromParcelWithoutChildren(in, Objects.requireNonNull(reader.mStringReader), + Objects.requireNonNull(reader.mTmpMatrix)); + if ((mFlags & FLAGS_HAS_CHILDREN) != 0) { + final int numChildren = in.readInt(); + if (DEBUG_PARCEL_TREE || DEBUG_PARCEL_CHILDREN) { + Log.d(TAG, + "Preparing to read " + numChildren + + " children: @ #" + reader.mNumReadViews + + ", level " + nestingLevel); + } + mChildren = new ViewNode[numChildren]; + for (int i = 0; i < numChildren; i++) { + mChildren[i] = new ViewNode(reader, nestingLevel + 1); + } + } + } + + private static void writeString(@NonNull Parcel out, @Nullable PooledStringWriter pwriter, + @Nullable String str) { + if (pwriter != null) { + pwriter.writeString(str); + } else { + out.writeString(str); + } + } + + @Nullable + private static String readString(@NonNull Parcel in, @Nullable PooledStringReader preader) { + if (preader != null) { + return preader.readString(); + } + return in.readString(); + } + + // This does not read the child nodes. + void initializeFromParcelWithoutChildren(Parcel in, @Nullable PooledStringReader preader, + @Nullable float[] tmpMatrix) { + mClassName = readString(in, preader); mFlags = in.readInt(); final int flags = mFlags; mAutofillFlags = in.readInt(); @@ -736,10 +779,10 @@ public class AssistStructure implements Parcelable { if ((flags&FLAGS_HAS_ID) != 0) { mId = in.readInt(); if (mId != View.NO_ID) { - mIdEntry = preader.readString(); + mIdEntry = readString(in, preader); if (mIdEntry != null) { - mIdType = preader.readString(); - mIdPackage = preader.readString(); + mIdType = readString(in, preader); + mIdPackage = readString(in, preader); } } } @@ -784,10 +827,10 @@ public class AssistStructure implements Parcelable { mMaxLength = in.readInt(); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_TEXT_ID_ENTRY) != 0) { - mTextIdEntry = preader.readString(); + mTextIdEntry = readString(in, preader); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_HINT_ID_ENTRY) != 0) { - mHintIdEntry = preader.readString(); + mHintIdEntry = readString(in, preader); } } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { @@ -809,8 +852,11 @@ public class AssistStructure implements Parcelable { } if ((flags&FLAGS_HAS_MATRIX) != 0) { mMatrix = new Matrix(); - in.readFloatArray(reader.mTmpMatrix); - mMatrix.setValues(reader.mTmpMatrix); + if (tmpMatrix == null) { + tmpMatrix = new float[9]; + } + in.readFloatArray(tmpMatrix); + mMatrix.setValues(tmpMatrix); } if ((flags&FLAGS_HAS_ELEVATION) != 0) { mElevation = in.readFloat(); @@ -836,24 +882,22 @@ public class AssistStructure implements Parcelable { if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) { mLocaleList = in.readParcelable(null); } + if ((flags & FLAGS_HAS_MIME_TYPES) != 0) { + mOnReceiveContentMimeTypes = in.readStringArray(); + } if ((flags&FLAGS_HAS_EXTRAS) != 0) { mExtras = in.readBundle(); } - if ((flags&FLAGS_HAS_CHILDREN) != 0) { - final int NCHILDREN = in.readInt(); - if (DEBUG_PARCEL_TREE || DEBUG_PARCEL_CHILDREN) Log.d(TAG, - "Preparing to read " + NCHILDREN - + " children: @ #" + reader.mNumReadViews - + ", level " + nestingLevel); - mChildren = new ViewNode[NCHILDREN]; - for (int i=0; i<NCHILDREN; i++) { - mChildren[i] = new ViewNode(reader, nestingLevel + 1); - } - } } - int writeSelfToParcel(Parcel out, PooledStringWriter pwriter, boolean sanitizeOnWrite, - float[] tmpMatrix) { + /** + * This does not write the child nodes. + * + * @param willWriteChildren whether child nodes will be written to the parcel or not after + * calling this method. + */ + int writeSelfToParcel(@NonNull Parcel out, @Nullable PooledStringWriter pwriter, + boolean sanitizeOnWrite, @Nullable float[] tmpMatrix, boolean willWriteChildren) { // Guard used to skip non-sanitized data when writing for autofill. boolean writeSensitive = true; @@ -900,10 +944,13 @@ public class AssistStructure implements Parcelable { if (mLocaleList != null) { flags |= FLAGS_HAS_LOCALE_LIST; } + if (mOnReceiveContentMimeTypes != null) { + flags |= FLAGS_HAS_MIME_TYPES; + } if (mExtras != null) { flags |= FLAGS_HAS_EXTRAS; } - if (mChildren != null) { + if (mChildren != null && willWriteChildren) { flags |= FLAGS_HAS_CHILDREN; } if (mAutofillId != null) { @@ -946,7 +993,7 @@ public class AssistStructure implements Parcelable { autofillFlags |= AUTOFILL_FLAGS_HAS_HINT_ID_ENTRY; } - pwriter.writeString(mClassName); + writeString(out, pwriter, mClassName); int writtenFlags = flags; if (autofillFlags != 0 && (mSanitized || !sanitizeOnWrite)) { @@ -966,10 +1013,10 @@ public class AssistStructure implements Parcelable { if ((flags&FLAGS_HAS_ID) != 0) { out.writeInt(mId); if (mId != View.NO_ID) { - pwriter.writeString(mIdEntry); + writeString(out, pwriter, mIdEntry); if (mIdEntry != null) { - pwriter.writeString(mIdType); - pwriter.writeString(mIdPackage); + writeString(out, pwriter, mIdType); + writeString(out, pwriter, mIdPackage); } } } @@ -1020,10 +1067,10 @@ public class AssistStructure implements Parcelable { out.writeInt(mMaxLength); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_TEXT_ID_ENTRY) != 0) { - pwriter.writeString(mTextIdEntry); + writeString(out, pwriter, mTextIdEntry); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_HINT_ID_ENTRY) != 0) { - pwriter.writeString(mHintIdEntry); + writeString(out, pwriter, mHintIdEntry); } } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { @@ -1040,6 +1087,9 @@ public class AssistStructure implements Parcelable { out.writeInt(mScrollY); } if ((flags&FLAGS_HAS_MATRIX) != 0) { + if (tmpMatrix == null) { + tmpMatrix = new float[9]; + } mMatrix.getValues(tmpMatrix); out.writeFloatArray(tmpMatrix); } @@ -1067,6 +1117,9 @@ public class AssistStructure implements Parcelable { if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) { out.writeParcelable(mLocaleList, 0); } + if ((flags & FLAGS_HAS_MIME_TYPES) != 0) { + out.writeStringArray(mOnReceiveContentMimeTypes); + } if ((flags&FLAGS_HAS_EXTRAS) != 0) { out.writeBundle(mExtras); } @@ -1485,6 +1538,15 @@ public class AssistStructure implements Parcelable { } /** + * Returns the MIME types accepted by {@link View#performReceiveContent} for this view. See + * {@link View#getOnReceiveContentMimeTypes()} for details. + */ + @Nullable + public String[] getOnReceiveContentMimeTypes() { + return mOnReceiveContentMimeTypes; + } + + /** * Returns any text associated with the node that is displayed to the user, or null * if there is none. */ @@ -1695,6 +1757,57 @@ public class AssistStructure implements Parcelable { } /** + * A parcelable wrapper class around {@link ViewNode}. + * + * <p>This class, when parceled and unparceled, does not carry the child nodes. + * + * @hide + */ + public static final class ViewNodeParcelable implements Parcelable { + + @NonNull + private final ViewNode mViewNode; + + public ViewNodeParcelable(@NonNull ViewNode viewNode) { + mViewNode = viewNode; + } + + public ViewNodeParcelable(@NonNull Parcel in) { + mViewNode = new ViewNode(in); + } + + @NonNull + public ViewNode getViewNode() { + return mViewNode; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + mViewNode.writeSelfToParcel(parcel, /*pwriter=*/null, /*sanitizeOnWrite=*/false, + /*tmpMatrix*/null, /*willWriteChildren=*/ false); + } + + @NonNull + public static final Parcelable.Creator<ViewNodeParcelable> CREATOR = + new Parcelable.Creator<ViewNodeParcelable>() { + @Override + public ViewNodeParcelable createFromParcel(@NonNull Parcel in) { + return new ViewNodeParcelable(in); + } + + @Override + public ViewNodeParcelable[] newArray(int size) { + return new ViewNodeParcelable[size]; + } + }; + } + + /** * POJO used to override some autofill-related values when the node is parcelized. * * @hide @@ -1704,17 +1817,35 @@ public class AssistStructure implements Parcelable { public AutofillValue value; } - static class ViewNodeBuilder extends ViewStructure { + /** + * @hide + */ + public static class ViewNodeBuilder extends ViewStructure { final AssistStructure mAssist; final ViewNode mNode; final boolean mAsync; + /** + * Used to instantiate a builder for a stand-alone {@link ViewNode} which is not associated + * to a properly created {@link AssistStructure}. + */ + public ViewNodeBuilder() { + mAssist = new AssistStructure(); + mNode = new ViewNode(); + mAsync = false; + } + ViewNodeBuilder(AssistStructure assist, ViewNode node, boolean async) { mAssist = assist; mNode = node; mAsync = async; } + @NonNull + public ViewNode getViewNode() { + return mNode; + } + @Override public void setId(int id, String packageName, String typeName, String entryName) { mNode.mId = id; @@ -2035,6 +2166,11 @@ public class AssistStructure implements Parcelable { } @Override + public void setOnReceiveContentMimeTypes(@Nullable String[] mimeTypes) { + mNode.mOnReceiveContentMimeTypes = mimeTypes; + } + + @Override public void setInputType(int inputType) { mNode.mInputType = inputType; } @@ -2311,6 +2447,10 @@ public class AssistStructure implements Parcelable { if (localeList != null) { Log.i(TAG, prefix + " LocaleList: " + localeList); } + String[] mimeTypes = node.getOnReceiveContentMimeTypes(); + if (mimeTypes != null) { + Log.i(TAG, prefix + " MIME types: " + Arrays.toString(mimeTypes)); + } String hint = node.getHint(); if (hint != null) { Log.i(TAG, prefix + " Hint: " + hint); diff --git a/location/java/android/location/IGnssAntennaInfoListener.aidl b/core/java/android/app/people/ConversationStatus.aidl index 603ed6a2614e..acfe1356304c 100644 --- a/location/java/android/location/IGnssAntennaInfoListener.aidl +++ b/core/java/android/app/people/ConversationStatus.aidl @@ -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,13 +14,6 @@ * limitations under the License. */ -package android.location; - -import android.location.GnssAntennaInfo; +package android.app.people; -/** - * {@hide} - */ -oneway interface IGnssAntennaInfoListener { - void onGnssAntennaInfoReceived(in List<GnssAntennaInfo> gnssAntennaInfo); -}
\ No newline at end of file +parcelable ConversationStatus;
\ No newline at end of file diff --git a/core/java/android/app/people/ConversationStatus.java b/core/java/android/app/people/ConversationStatus.java new file mode 100644 index 000000000000..d2a0255d572e --- /dev/null +++ b/core/java/android/app/people/ConversationStatus.java @@ -0,0 +1,240 @@ +/* + * 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.app.people; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.drawable.Icon; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +public final class ConversationStatus implements Parcelable { + private static final String TAG = "ConversationStatus"; + + /** @hide */ + @IntDef(prefix = { "ACTIVITY_" }, value = { + ACTIVITY_OTHER, + ACTIVITY_BIRTHDAY, + ACTIVITY_ANNIVERSARY, + ACTIVITY_NEW_STORY, + ACTIVITY_MEDIA, + ACTIVITY_GAME, + ACTIVITY_LOCATION, + ACTIVITY_UPCOMING_BIRTHDAY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ActivityType {} + + public static final int ACTIVITY_OTHER = 0; + public static final int ACTIVITY_BIRTHDAY = 1; + public static final int ACTIVITY_ANNIVERSARY = 2; + public static final int ACTIVITY_NEW_STORY = 3; + public static final int ACTIVITY_MEDIA = 4; + public static final int ACTIVITY_GAME = 5; + public static final int ACTIVITY_LOCATION = 6; + public static final int ACTIVITY_UPCOMING_BIRTHDAY = 7; + + /** @hide */ + @IntDef(prefix = { "AVAILABILITY_" }, value = { + AVAILABILITY_UNKNOWN, + AVAILABILITY_AVAILABLE, + AVAILABILITY_BUSY, + AVAILABILITY_OFFLINE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Availability {} + + public static final int AVAILABILITY_UNKNOWN = -1; + public static final int AVAILABILITY_AVAILABLE = 0; + public static final int AVAILABILITY_BUSY = 1; + public static final int AVAILABILITY_OFFLINE = 2; + + private final String mId; + private final int mActivity; + + private int mAvailability; + private CharSequence mDescription; + private Icon mIcon; + private long mStartTimeMs; + private long mEndTimeMs; + + private ConversationStatus(Builder b) { + mId = b.mId; + mActivity = b.mActivity; + mAvailability = b.mAvailability; + mDescription = b.mDescription; + mIcon = b.mIcon; + mStartTimeMs = b.mStartTimeMs; + mEndTimeMs = b.mEndTimeMs; + } + + private ConversationStatus(Parcel p) { + mId = p.readString(); + mActivity = p.readInt(); + mAvailability = p.readInt(); + mDescription = p.readCharSequence(); + mIcon = p.readParcelable(Icon.class.getClassLoader()); + mStartTimeMs = p.readLong(); + mEndTimeMs = p.readLong(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mId); + dest.writeInt(mActivity); + dest.writeInt(mAvailability); + dest.writeCharSequence(mDescription); + dest.writeParcelable(mIcon, flags); + dest.writeLong(mStartTimeMs); + dest.writeLong(mEndTimeMs); + } + + public @NonNull String getId() { + return mId; + } + + public @ActivityType int getActivity() { + return mActivity; + } + + public @Availability + int getAvailability() { + return mAvailability; + } + + public @Nullable + CharSequence getDescription() { + return mDescription; + } + + public @Nullable Icon getIcon() { + return mIcon; + } + + public long getStartTimeMillis() { + return mStartTimeMs; + } + + public long getEndTimeMillis() { + return mEndTimeMs; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConversationStatus that = (ConversationStatus) o; + return mActivity == that.mActivity && + mAvailability == that.mAvailability && + mStartTimeMs == that.mStartTimeMs && + mEndTimeMs == that.mEndTimeMs && + mId.equals(that.mId) && + Objects.equals(mDescription, that.mDescription) && + Objects.equals(mIcon, that.mIcon); + } + + @Override + public int hashCode() { + return Objects.hash(mId, mActivity, mAvailability, mDescription, mIcon, mStartTimeMs, + mEndTimeMs); + } + + @Override + public String toString() { + return "ConversationStatus{" + + "mId='" + mId + '\'' + + ", mActivity=" + mActivity + + ", mAvailability=" + mAvailability + + ", mDescription=" + mDescription + + ", mIcon=" + mIcon + + ", mStartTimeMs=" + mStartTimeMs + + ", mEndTimeMs=" + mEndTimeMs + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Creator<ConversationStatus> CREATOR + = new Creator<ConversationStatus>() { + public ConversationStatus createFromParcel(Parcel source) { + return new ConversationStatus(source); + } + + public ConversationStatus[] newArray(int size) { + return new ConversationStatus[size]; + } + }; + + public static final class Builder { + final String mId; + final int mActivity; + int mAvailability = AVAILABILITY_UNKNOWN; + CharSequence mDescription; + Icon mIcon; + long mStartTimeMs = -1; + long mEndTimeMs = -1; + + /** + * Creates a new builder. + * + * @param id The unique id for this status + * @param activity The type of status + */ + public Builder(@NonNull String id, @ActivityType @NonNull int activity) { + mId = id; + mActivity = activity; + } + + + public @NonNull Builder setAvailability(@Availability int availability) { + mAvailability = availability; + return this; + } + + public @NonNull Builder setDescription(@Nullable CharSequence description) { + mDescription = description; + return this; + } + + public @NonNull Builder setIcon(@Nullable Icon icon) { + mIcon = icon; + return this; + } + + public @NonNull Builder setStartTimeMillis(long startTimeMs) { + mStartTimeMs = startTimeMs; + return this; + } + + public @NonNull Builder setEndTimeMillis(long endTimeMs) { + mEndTimeMs = endTimeMs; + return this; + } + + public @NonNull ConversationStatus build() { + return new ConversationStatus(this); + } + } +} diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl index c547ef1e7e9b..0d12ed02f610 100644 --- a/core/java/android/app/people/IPeopleManager.aidl +++ b/core/java/android/app/people/IPeopleManager.aidl @@ -16,6 +16,7 @@ package android.app.people; +import android.app.people.ConversationStatus; import android.content.pm.ParceledListSlice; import android.net.Uri; import android.os.IBinder; @@ -45,4 +46,9 @@ interface IPeopleManager { * conversation can't be found or no interactions have been recorded, returns 0L. */ long getLastInteraction(in String packageName, int userId, in String shortcutId); + + void addOrUpdateStatus(in String packageName, int userId, in String conversationId, in ConversationStatus status); + void clearStatus(in String packageName, int userId, in String conversationId, in String statusId); + void clearStatuses(in String packageName, int userId, in String conversationId); + ParceledListSlice getStatuses(in String packageName, int userId, in String conversationId); } diff --git a/core/java/android/app/people/PeopleManager.java b/core/java/android/app/people/PeopleManager.java new file mode 100644 index 000000000000..de7ba622ae88 --- /dev/null +++ b/core/java/android/app/people/PeopleManager.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.people; + +import android.annotation.NonNull; +import android.annotation.SystemService; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.content.pm.ShortcutInfo; +import android.os.RemoteException; +import android.os.ServiceManager; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * This class allows interaction with conversation and people data. + */ +@SystemService(Context.PEOPLE_SERVICE) +public final class PeopleManager { + + private static final String LOG_TAG = PeopleManager.class.getSimpleName(); + + @NonNull + private final Context mContext; + + @NonNull + private final IPeopleManager mService; + + /** + * @hide + */ + public PeopleManager(@NonNull Context context) throws ServiceManager.ServiceNotFoundException { + mContext = context; + mService = IPeopleManager.Stub.asInterface(ServiceManager.getServiceOrThrow( + Context.PEOPLE_SERVICE)); + } + + + /** + * Sets or updates a {@link ConversationStatus} for a conversation. + * + * <p>Statuses are meant to represent current information about the conversation. Like + * notifications, they are transient and are not persisted beyond a reboot, nor are they + * backed up and restored.</p> + * <p>If the provided conversation shortcut is not already pinned, or cached by the system, + * it will remain cached as long as the status is active.</p> + * + * @param conversationId the {@link ShortcutInfo#getId() id} of the shortcut backing the + * conversation that has an active status + * @param status the current status for the given conversation + * + * @return whether the role is available in the system + */ + public void addOrUpdateStatus(@NonNull String conversationId, + @NonNull ConversationStatus status) { + Preconditions.checkStringNotEmpty(conversationId); + Objects.requireNonNull(status); + try { + mService.addOrUpdateStatus( + mContext.getPackageName(), mContext.getUserId(), conversationId, status); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unpublishes a given status from the given conversation. + * + * @param conversationId the {@link ShortcutInfo#getId() id} of the shortcut backing the + * conversation that has an active status + * @param statusId the {@link ConversationStatus#getId() id} of a published status for the given + * conversation + */ + public void clearStatus(@NonNull String conversationId, @NonNull String statusId) { + Preconditions.checkStringNotEmpty(conversationId); + Preconditions.checkStringNotEmpty(statusId); + try { + mService.clearStatus( + mContext.getPackageName(), mContext.getUserId(), conversationId, statusId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes all published statuses for the given conversation. + * + * @param conversationId the {@link ShortcutInfo#getId() id} of the shortcut backing the + * conversation that has one or more active statuses + */ + public void clearStatuses(@NonNull String conversationId) { + Preconditions.checkStringNotEmpty(conversationId); + try { + mService.clearStatuses( + mContext.getPackageName(), mContext.getUserId(), conversationId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns all of the currently published statuses for a given conversation. + * + * @param conversationId the {@link ShortcutInfo#getId() id} of the shortcut backing the + * conversation that has one or more active statuses + */ + public @NonNull List<ConversationStatus> getStatuses(@NonNull String conversationId) { + try { + final ParceledListSlice<ConversationStatus> parceledList + = mService.getStatuses( + mContext.getPackageName(), mContext.getUserId(), conversationId); + if (parceledList != null) { + return parceledList.getList(); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return new ArrayList<>(); + } +} diff --git a/core/java/android/app/time/LocationTimeZoneManager.java b/core/java/android/app/time/LocationTimeZoneManager.java index d909c0cb1eba..71a800f2085e 100644 --- a/core/java/android/app/time/LocationTimeZoneManager.java +++ b/core/java/android/app/time/LocationTimeZoneManager.java @@ -40,17 +40,58 @@ public final class LocationTimeZoneManager { public static final String SHELL_COMMAND_SERVICE_NAME = "location_time_zone_manager"; /** - * Shell command that starts the service (after stop). + * A shell command that starts the service (after stop). */ public static final String SHELL_COMMAND_START = "start"; /** - * Shell command that stops the service. + * A shell command that stops the service. */ public static final String SHELL_COMMAND_STOP = "stop"; /** - * Shell command that sends test commands to a provider + * A shell command that can put providers into different modes. Takes effect next time the + * service is started. + */ + public static final String SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE = + "set_provider_mode_override"; + + /** + * The default provider mode. + * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}. + */ + public static final String PROVIDER_MODE_OVERRIDE_NONE = "none"; + + /** + * The "simulated" provider mode. + * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}. + */ + public static final String PROVIDER_MODE_OVERRIDE_SIMULATED = "simulated"; + + /** + * The "disabled" provider mode (equivalent to there being no provider configured). + * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}. + */ + public static final String PROVIDER_MODE_OVERRIDE_DISABLED = "disabled"; + + /** + * A shell command that tells the service to record state information during tests. The next + * argument value is "true" or "false". + */ + public static final String SHELL_COMMAND_RECORD_PROVIDER_STATES = "record_provider_states"; + + /** + * A shell command that tells the service to dump its current state. + */ + public static final String SHELL_COMMAND_DUMP_STATE = "dump_state"; + + /** + * Option for {@link #SHELL_COMMAND_DUMP_STATE} that tells it to dump state as a binary proto. + */ + public static final String DUMP_STATE_OPTION_PROTO = "--proto"; + + /** + * A shell command that sends test commands to a provider */ public static final String SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND = "send_provider_test_command"; @@ -88,35 +129,6 @@ public final class LocationTimeZoneManager { */ public static final String SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN = "uncertain"; - private static final String SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PREFIX = - "persist.sys.geotz."; - - /** - * The name of the system property that can be used to set the primary provider into test mode - * (value = {@link #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED}) or disabled (value = {@link - * #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED}). - */ - public static final String SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY = - SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PREFIX + PRIMARY_PROVIDER_NAME; - - /** - * The name of the system property that can be used to set the secondary provider into test mode - * (value = {@link #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED}) or disabled (value = {@link - * #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED}). - */ - public static final String SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY = - SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PREFIX + SECONDARY_PROVIDER_NAME; - - /** - * The value of the provider mode system property to put a provider into test mode. - */ - public static final String SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED = "simulated"; - - /** - * The value of the provider mode system property to put a provider into disabled mode. - */ - public static final String SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED = "disabled"; - private LocationTimeZoneManager() { // No need to instantiate. } diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java index e0b7ffed1b36..ee718b35d4c5 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -29,11 +29,69 @@ import android.content.Context; @SystemService(Context.TIME_ZONE_DETECTOR_SERVICE) public interface TimeZoneDetector { - /** @hide */ + /** + * The name of the service for shell commands. + * @hide + */ + String SHELL_COMMAND_SERVICE_NAME = "time_zone_detector"; + + /** + * A shell command that prints the current "auto time zone detection" global setting value. + * @hide + */ + String SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED = "is_auto_detection_enabled"; + + /** + * A shell command that sets the current "auto time zone detection" global setting value. + * @hide + */ + String SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED = "set_auto_detection_enabled"; + + /** + * A shell command that prints whether the geolocation-based time zone detection feature is + * supported on the device. + * @hide + */ + String SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED = "is_geo_detection_supported"; + + /** + * A shell command that prints the current user's "location enabled" setting. + * @hide + */ + String SHELL_COMMAND_IS_LOCATION_ENABLED = "is_location_enabled"; + + /** + * A shell command that prints the current user's "location-based time zone detection enabled" + * setting. + * @hide + */ + String SHELL_COMMAND_IS_GEO_DETECTION_ENABLED = "is_geo_detection_enabled"; + + /** + * A shell command that sets the current user's "location-based time zone detection enabled" + * setting. + * @hide + */ + String SHELL_COMMAND_SET_GEO_DETECTION_ENABLED = "set_geo_detection_enabled"; + + /** + * A shell command that injects a geolocation time zone suggestion (as if from the + * location_time_zone_manager). + * @hide + */ String SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE = "suggest_geo_location_time_zone"; - /** @hide */ + + /** + * A shell command that injects a manual time zone suggestion (as if from the SettingsUI or + * similar). + * @hide + */ String SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE = "suggest_manual_time_zone"; - /** @hide */ + + /** + * A shell command that injects a telephony time zone suggestion (as if from the phone app). + * @hide + */ String SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE = "suggest_telephony_time_zone"; /** diff --git a/core/java/android/apphibernation/OWNERS b/core/java/android/apphibernation/OWNERS new file mode 100644 index 000000000000..587c719f5587 --- /dev/null +++ b/core/java/android/apphibernation/OWNERS @@ -0,0 +1,2 @@ +kevhan@google.com +rajekumar@google.com diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index 7a6ff79623af..381318b26dad 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -824,6 +824,25 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { 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 13b1b4f93cf0..088b0169b631 100644 --- a/core/java/android/bluetooth/BluetoothGattServer.java +++ b/core/java/android/bluetooth/BluetoothGattServer.java @@ -443,6 +443,25 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { 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 3b4fe0a30b80..d5c1c3e2d61e 100644 --- a/core/java/android/bluetooth/BluetoothManager.java +++ b/core/java/android/bluetooth/BluetoothManager.java @@ -225,6 +225,24 @@ public final class BluetoothManager { * * @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 final class BluetoothManager { */ 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 @@ public final class BluetoothManager { 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/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index 56c482471003..c0736a6b7bba 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -162,6 +162,11 @@ public final class BluetoothUuid { /** @hide */ @NonNull @SystemApi + public static final ParcelUuid DIP = + ParcelUuid.fromString("00001200-0000-1000-8000-00805F9B34FB"); + /** @hide */ + @NonNull + @SystemApi public static final ParcelUuid BASE_UUID = ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); diff --git a/core/java/android/bluetooth/SdpDipRecord.java b/core/java/android/bluetooth/SdpDipRecord.java new file mode 100644 index 000000000000..84b0eef0593e --- /dev/null +++ b/core/java/android/bluetooth/SdpDipRecord.java @@ -0,0 +1,104 @@ +/* +* Copyright (C) 2015 Samsung System LSI +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package android.bluetooth; + +import java.util.Arrays; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Data representation of a Object Push Profile Server side SDP record. + */ +/** @hide */ +public class SdpDipRecord implements Parcelable { + private final int mSpecificationId; + private final int mVendorId; + private final int mVendorIdSource; + private final int mProductId; + private final int mVersion; + private final boolean mPrimaryRecord; + + public SdpDipRecord(int specificationId, + int vendorId, int vendorIdSource, + int productId, int version, + boolean primaryRecord) { + super(); + this.mSpecificationId = specificationId; + this.mVendorId = vendorId; + this.mVendorIdSource = vendorIdSource; + this.mProductId = productId; + this.mVersion = version; + this.mPrimaryRecord = primaryRecord; + } + + public SdpDipRecord(Parcel in) { + this.mSpecificationId = in.readInt(); + this.mVendorId = in.readInt(); + this.mVendorIdSource = in.readInt(); + this.mProductId = in.readInt(); + this.mVersion = in.readInt(); + this.mPrimaryRecord = in.readBoolean(); + } + + public int getSpecificationId() { + return mSpecificationId; + } + + public int getVendorId() { + return mVendorId; + } + + public int getVendorIdSource() { + return mVendorIdSource; + } + + public int getProductId() { + return mProductId; + } + + public int getVersion() { + return mVersion; + } + + public boolean getPrimaryRecord() { + return mPrimaryRecord; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mSpecificationId); + dest.writeInt(mVendorId); + dest.writeInt(mVendorIdSource); + dest.writeInt(mProductId); + dest.writeInt(mVersion); + dest.writeBoolean(mPrimaryRecord); + } + + @Override + public int describeContents() { + /* No special objects */ + return 0; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public SdpDipRecord createFromParcel(Parcel in) { + return new SdpDipRecord(in); + } + public SdpDipRecord[] newArray(int size) { + return new SdpDipRecord[size]; + } + }; +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 29ffa0b70313..5ccceca9a69d 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -40,6 +40,7 @@ import android.app.ActivityManager; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.VrManager; +import android.app.people.PeopleManager; import android.app.time.TimeManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ApplicationInfo; @@ -4508,6 +4509,17 @@ public abstract class Context { 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 @@ -5311,10 +5323,10 @@ public abstract class Context { public static final String SMS_SERVICE = "sms"; /** - * Use with {@link #getSystemService(String)} to access people service. + * Use with {@link #getSystemService(String)} to access a {@link PeopleManager} to interact + * with your published conversations. * * @see #getSystemService(String) - * @hide */ public static final String PEOPLE_SERVICE = "people"; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index e074eab99a7c..17c4d25d82d7 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3406,6 +3406,14 @@ public abstract class PackageManager { /** * 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/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl index 4e368d0610b3..a54e88f2d493 100644 --- a/core/java/android/hardware/ISensorPrivacyManager.aidl +++ b/core/java/android/hardware/ISensorPrivacyManager.aidl @@ -26,18 +26,19 @@ interface ISensorPrivacyManager { // =============== Beginning of transactions used on native side as well ====================== void addSensorPrivacyListener(in ISensorPrivacyListener listener); + void addIndividualSensorPrivacyListener(int userId, int sensor, + in ISensorPrivacyListener listener); + void removeSensorPrivacyListener(in ISensorPrivacyListener listener); boolean isSensorPrivacyEnabled(); - void setSensorPrivacy(boolean enable); - // =============== End of transactions used on native side as well ============================ + boolean isIndividualSensorPrivacyEnabled(int userId, int sensor); - // TODO(evanseverson) add to native interface - boolean isIndividualSensorPrivacyEnabled(int sensor); + void setSensorPrivacy(boolean enable); - // TODO(evanseverson) add to native interface - void setIndividualSensorPrivacy(int sensor, boolean enable); + void setIndividualSensorPrivacy(int userId, int sensor, boolean enable); - // TODO(evanseverson) listeners + void setIndividualSensorPrivacyForProfileGroup(int userId, int sensor, boolean enable); + // =============== End of transactions used on native side as well ============================ }
\ No newline at end of file diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index c647239d9049..8497525f85b3 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -160,6 +160,37 @@ public final class SensorPrivacyManager { } /** + * Registers a new listener to receive notification when the state of sensor privacy + * changes. + * + * @param sensor the sensor to listen to changes to + * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor + * privacy changes. + */ + public void addSensorPrivacyListener(@IndividualSensor int sensor, + final OnSensorPrivacyChangedListener listener) { + synchronized (mListeners) { + ISensorPrivacyListener iListener = mListeners.get(listener); + if (iListener == null) { + iListener = new ISensorPrivacyListener.Stub() { + @Override + public void onSensorPrivacyChanged(boolean enabled) { + listener.onSensorPrivacyChanged(enabled); + } + }; + mListeners.put(listener, iListener); + } + + try { + mService.addIndividualSensorPrivacyListener(mContext.getUserId(), sensor, + iListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** * Unregisters the specified listener from receiving notifications when the state of sensor * privacy changes. * @@ -200,7 +231,7 @@ public final class SensorPrivacyManager { */ public boolean isIndividualSensorPrivacyEnabled(@IndividualSensor int sensor) { try { - return mService.isIndividualSensorPrivacyEnabled(sensor); + return mService.isIndividualSensorPrivacyEnabled(mContext.getUserId(), sensor); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -209,12 +240,32 @@ public final class SensorPrivacyManager { /** * Sets sensor privacy to the specified state for an individual sensor. * + * @param sensor the sensor which to change the state for + * @param enable the state to which sensor privacy should be set. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) + public void setIndividualSensorPrivacy(@IndividualSensor int sensor, + boolean enable) { + try { + mService.setIndividualSensorPrivacy(mContext.getUserId(), sensor, enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets sensor privacy to the specified state for an individual sensor for the profile group of + * context's user. + * + * @param sensor the sensor which to change the state for * @param enable the state to which sensor privacy should be set. */ @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) - public void setIndividualSensorPrivacy(@IndividualSensor int sensor, boolean enable) { + public void setIndividualSensorPrivacyForProfileGroup(@IndividualSensor int sensor, + boolean enable) { try { - mService.setIndividualSensorPrivacy(sensor, enable); + mService.setIndividualSensorPrivacyForProfileGroup(mContext.getUserId(), sensor, + enable); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/biometrics/OWNERS b/core/java/android/hardware/biometrics/OWNERS index 33527f824827..2065ffacca7c 100644 --- a/core/java/android/hardware/biometrics/OWNERS +++ b/core/java/android/hardware/biometrics/OWNERS @@ -1,3 +1,8 @@ # 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/face/OWNERS b/core/java/android/hardware/face/OWNERS index 33527f824827..be10df1099ed 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/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index a4e573876218..582570e63d54 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -568,11 +568,13 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @param cancel an object that can be used to cancel enrollment * @param userId the user to whom this fingerprint will belong to * @param callback an object to receive enrollment events + * @param shouldLogMetrics a flag that indicates if enrollment failure/success metrics + * should be logged. * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) public void enroll(byte [] hardwareAuthToken, CancellationSignal cancel, int userId, - EnrollmentCallback callback) { + EnrollmentCallback callback, boolean shouldLogMetrics) { if (userId == UserHandle.USER_CURRENT) { userId = getCurrentUserId(); } @@ -593,7 +595,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing try { mEnrollmentCallback = callback; mService.enroll(mToken, hardwareAuthToken, userId, mServiceReceiver, - mContext.getOpPackageName()); + mContext.getOpPackageName(), shouldLogMetrics); } catch (RemoteException e) { Slog.w(TAG, "Remote exception in enroll: ", e); // Though this may not be a hardware issue, it will cause apps to give up or try diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 64abbea12de0..ac026c796b51 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -77,7 +77,7 @@ interface IFingerprintService { // Start fingerprint enrollment void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver, - String opPackageName); + String opPackageName, boolean shouldLogMetrics); // Cancel enrollment in progress void cancelEnrollment(IBinder token); diff --git a/core/java/android/hardware/fingerprint/OWNERS b/core/java/android/hardware/fingerprint/OWNERS index dcead40d482d..e55b8c564ddb 100644 --- a/core/java/android/hardware/fingerprint/OWNERS +++ b/core/java/android/hardware/fingerprint/OWNERS @@ -1,3 +1,8 @@ # Bug component: 114777 +curtislb@google.com +ilyamaty@google.com jaggies@google.com +joshmccloskey@google.com +kchyn@google.com + diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 36348b365158..8bfbad605420 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -442,6 +442,24 @@ public class NetworkPolicyManager { } /** + * 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 6209718e8788..66b99b9ba319 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -40,6 +40,18 @@ import java.util.Set; */ 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/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index bf25602041cf..f41306301d42 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -45,8 +45,8 @@ import com.android.internal.util.IndentingPrintWriter; import libcore.util.EmptyArray; import java.io.CharArrayWriter; -import java.io.DataInputStream; -import java.io.DataOutputStream; +import java.io.DataInput; +import java.io.DataOutput; import java.io.IOException; import java.io.PrintWriter; import java.net.ProtocolException; @@ -162,7 +162,7 @@ public class NetworkStatsHistory implements Parcelable { out.writeLong(totalBytes); } - public NetworkStatsHistory(DataInputStream in) throws IOException { + public NetworkStatsHistory(DataInput in) throws IOException { final int version = in.readInt(); switch (version) { case VERSION_INIT: { @@ -204,7 +204,7 @@ public class NetworkStatsHistory implements Parcelable { } } - public void writeToStream(DataOutputStream out) throws IOException { + public void writeToStream(DataOutput out) throws IOException { out.writeInt(VERSION_ADD_ACTIVE); out.writeLong(bucketDuration); writeVarLongArray(out, bucketStart, bucketCount); @@ -768,7 +768,7 @@ public class NetworkStatsHistory implements Parcelable { */ public static class DataStreamUtils { @Deprecated - public static long[] readFullLongArray(DataInputStream in) throws IOException { + public static long[] readFullLongArray(DataInput in) throws IOException { final int size = in.readInt(); if (size < 0) throw new ProtocolException("negative array size"); final long[] values = new long[size]; @@ -781,7 +781,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Read variable-length {@link Long} using protobuf-style approach. */ - public static long readVarLong(DataInputStream in) throws IOException { + public static long readVarLong(DataInput in) throws IOException { int shift = 0; long result = 0; while (shift < 64) { @@ -797,7 +797,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Write variable-length {@link Long} using protobuf-style approach. */ - public static void writeVarLong(DataOutputStream out, long value) throws IOException { + public static void writeVarLong(DataOutput out, long value) throws IOException { while (true) { if ((value & ~0x7FL) == 0) { out.writeByte((int) value); @@ -809,7 +809,7 @@ public class NetworkStatsHistory implements Parcelable { } } - public static long[] readVarLongArray(DataInputStream in) throws IOException { + public static long[] readVarLongArray(DataInput in) throws IOException { final int size = in.readInt(); if (size == -1) return null; if (size < 0) throw new ProtocolException("negative array size"); @@ -820,7 +820,7 @@ public class NetworkStatsHistory implements Parcelable { return values; } - public static void writeVarLongArray(DataOutputStream out, long[] values, int size) + public static void writeVarLongArray(DataOutput out, long[] values, int size) throws IOException { if (values == null) { out.writeInt(-1); diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl index 9dd01140b413..04b585cdf420 100644 --- a/core/java/android/net/vcn/IVcnManagementService.aidl +++ b/core/java/android/net/vcn/IVcnManagementService.aidl @@ -23,6 +23,6 @@ import android.os.ParcelUuid; * @hide */ interface IVcnManagementService { - void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config); + void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config, in String opPkgName); void clearVcnConfig(in ParcelUuid subscriptionGroup); } diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java index d4a3fa7411b1..ede8faaaf261 100644 --- a/core/java/android/net/vcn/VcnConfig.java +++ b/core/java/android/net/vcn/VcnConfig.java @@ -19,6 +19,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; @@ -45,11 +46,17 @@ import java.util.Set; public final class VcnConfig implements Parcelable { @NonNull private static final String TAG = VcnConfig.class.getSimpleName(); + private static final String PACKAGE_NAME_KEY = "mPackageName"; + @NonNull private final String mPackageName; + private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs"; @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs; - private VcnConfig(@NonNull Set<VcnGatewayConnectionConfig> tunnelConfigs) { - mGatewayConnectionConfigs = Collections.unmodifiableSet(tunnelConfigs); + private VcnConfig( + @NonNull String packageName, + @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) { + mPackageName = packageName; + mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs); validate(); } @@ -61,6 +68,8 @@ public final class VcnConfig implements Parcelable { */ @VisibleForTesting(visibility = Visibility.PRIVATE) public VcnConfig(@NonNull PersistableBundle in) { + mPackageName = in.getString(PACKAGE_NAME_KEY); + final PersistableBundle gatewayConnectionConfigsBundle = in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY); mGatewayConnectionConfigs = @@ -72,8 +81,19 @@ public final class VcnConfig implements Parcelable { } private void validate() { + Objects.requireNonNull(mPackageName, "packageName was null"); Preconditions.checkCollectionNotEmpty( - mGatewayConnectionConfigs, "gatewayConnectionConfigs"); + mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty"); + } + + /** + * Retrieve the package name of the provisioning app. + * + * @hide + */ + @NonNull + public String getProvisioningPackageName() { + return mPackageName; } /** Retrieves the set of configured tunnels. */ @@ -91,6 +111,8 @@ public final class VcnConfig implements Parcelable { public PersistableBundle toPersistableBundle() { final PersistableBundle result = new PersistableBundle(); + result.putString(PACKAGE_NAME_KEY, mPackageName); + final PersistableBundle gatewayConnectionConfigsBundle = PersistableBundleUtils.fromList( new ArrayList<>(mGatewayConnectionConfigs), @@ -102,7 +124,7 @@ public final class VcnConfig implements Parcelable { @Override public int hashCode() { - return Objects.hash(mGatewayConnectionConfigs); + return Objects.hash(mPackageName, mGatewayConnectionConfigs); } @Override @@ -112,7 +134,8 @@ public final class VcnConfig implements Parcelable { } final VcnConfig rhs = (VcnConfig) other; - return mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs); + return mPackageName.equals(rhs.mPackageName) + && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs); } // Parcelable methods @@ -143,9 +166,17 @@ public final class VcnConfig implements Parcelable { /** This class is used to incrementally build {@link VcnConfig} objects. */ public static class Builder { + @NonNull private final String mPackageName; + @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>(); + public Builder(@NonNull Context context) { + Objects.requireNonNull(context, "context was null"); + + mPackageName = context.getOpPackageName(); + } + /** * Adds a configuration for an individual gateway connection. * @@ -168,7 +199,7 @@ public final class VcnConfig implements Parcelable { */ @NonNull public VcnConfig build() { - return new VcnConfig(mGatewayConnectionConfigs); + return new VcnConfig(mPackageName, mGatewayConnectionConfigs); } } } diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 19c183f9fe9c..b881a339535b 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -101,7 +101,7 @@ public final class VcnManager { requireNonNull(config, "config was null"); try { - mService.setVcnConfig(subscriptionGroup, config); + mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName()); } catch (ServiceSpecificException e) { throw new IOException(e); } catch (RemoteException e) { diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index bf8ac6e55d8a..ba29a15a91bb 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -39,6 +39,11 @@ public abstract class BatteryConsumer { 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 abstract class BatteryConsumer { 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 @@ public abstract class BatteryConsumer { 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 abstract class BatteryConsumer { 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/FileUtils.java b/core/java/android/os/FileUtils.java index c014ef682a24..0326b72ece7c 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -1463,8 +1463,7 @@ public final class FileUtils { Uri uri = MediaStore.scanFile(resolver, realFile); if (uri != null) { Bundle opts = new Bundle(); - // TODO(b/158465539): Use API constant - opts.putBoolean("android.provider.extra.ACCEPT_ORIGINAL_MEDIA_FORMAT", true); + opts.putBoolean(MediaStore.EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT, true); AssetFileDescriptor afd = resolver.openTypedAssetFileDescriptor(uri, "*/*", opts); Log.i(TAG, "Changed to modern format dataSource for: " + realFile); return afd.getFileDescriptor(); diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index b57418d751bc..c0b2ada7860c 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -1071,6 +1071,7 @@ public abstract class VibrationEffect implements Parcelable { PRIMITIVE_SLOW_RISE, PRIMITIVE_QUICK_FALL, PRIMITIVE_TICK, + PRIMITIVE_LOW_TICK, }) @Retention(RetentionPolicy.SOURCE) public @interface Primitive {} @@ -1116,6 +1117,12 @@ public abstract class VibrationEffect implements Parcelable { */ // 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 @@ public abstract class VibrationEffect implements Parcelable { * */ 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 @@ public abstract class VibrationEffect implements Parcelable { 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 ca92ad5deae6..7db5a80b6cf9 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -21,6 +21,7 @@ import android.content.pm.IDataLoaderStatusListener; 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 @@ interface IIncrementalService { 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 @@ interface IIncrementalService { /** * 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 284c2df2ee7b..59292baa110c 100644 --- a/core/java/android/os/incremental/IncrementalFileStorages.java +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -69,7 +69,8 @@ public final class IncrementalFileStorages { @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 @@ public final class IncrementalFileStorages { 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 @@ public final class IncrementalFileStorages { @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 @@ public final class IncrementalFileStorages { 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 @@ public final class IncrementalFileStorages { /** * 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 fb47ef04b231..4b9327021cb0 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.Path; 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 @@ public final class IncrementalManager { 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 b913faf9cc83..5b688bbd0655 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -432,9 +432,9 @@ public final class IncrementalStorage { /** * 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 000000000000..84f30a6c3aaf --- /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 8ac1fc1f23c7..b5abe2a3e311 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -163,7 +163,7 @@ public final class StorageVolume implements Parcelable { 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 3bee401dbd0d..7335e002e3b7 100644 --- a/core/java/android/permission/PermGroupUsage.java +++ b/core/java/android/permission/PermGroupUsage.java @@ -30,17 +30,19 @@ public final class PermGroupUsage { 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 @@ public final class PermGroupUsage { 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 6a8dca153585..00ba86732e55 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -153,11 +153,13 @@ public class PermissionUsageHelper { * @see PermissionManager.getIndicatorAppOpUsageData */ public List<PermGroupUsage> getOpUsageData(boolean isMicMuted) { + List<PermGroupUsage> usages = new ArrayList<>(); + if (!shouldShowIndicators()) { - return null; + return usages; } - List<String> ops = CAMERA_OPS; + List<String> ops = new ArrayList<>(CAMERA_OPS); if (shouldShowLocationIndicator()) { ops.addAll(LOCATION_OPS); } @@ -169,7 +171,6 @@ public class PermissionUsageHelper { Map<PackageAttribution, CharSequence> packagesWithAttributionLabels = getTrustedAttributions(rawUsages.get(MICROPHONE)); - List<PermGroupUsage> usages = new ArrayList<>(); List<String> usedPermGroups = new ArrayList<>(rawUsages.keySet()); for (int permGroupNum = 0; permGroupNum < usedPermGroups.size(); permGroupNum++) { boolean isPhone = false; @@ -186,7 +187,7 @@ public class PermissionUsageHelper { 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()))); } } @@ -240,7 +241,7 @@ public class PermissionUsageHelper { opEntry.getAttributedOpEntries().get(attributionTag); long lastAccessTime = attrOpEntry.getLastAccessTime(opFlags); - if (lastAccessTime < recentThreshold) { + if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) { continue; } if (!isUserSensitive(packageName, user, op) diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index ec4d81c10b5b..7a856e8a02b7 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -101,6 +101,13 @@ public final class DeviceConfig { public static final String NAMESPACE_APP_COMPAT = "app_compat"; /** + * Namespace for all app hibernation related features. + * @hide + */ + @SystemApi + public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation"; + + /** * Namespace for app standby configurations. * * @hide diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 69ca28a38fb6..9db7ca0cad6b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14630,6 +14630,34 @@ public final class Settings { public static final String BACKPORT_S_NOTIF_RULES = "backport_s_notif_rules"; /** + * The decoration to put on fully custom views that target S. + * + * <p>Values are: + * <br>0: DECORATION_NONE: no decorations. + * <br>1: DECORATION_MINIMAL: most minimal template; just the icon and the expander. + * <br>2: DECORATION_PARTIAL: basic template without the top line. + * <br>3: DECORATION_FULL_COMPATIBLE: basic template with the top line; 40dp of height. + * <br>4: DECORATION_FULL_CONSTRAINED: basic template with the top line; 28dp of height. + * <p>See {@link android.app.Notification.DevFlags} for more details. + * @hide + */ + public static final String FULLY_CUSTOM_VIEW_NOTIF_DECORATION = + "fully_custom_view_notif_decoration"; + + /** + * The decoration to put on decorated custom views that target S. + * + * <p>Values are: + * <br>2: DECORATION_PARTIAL: basic template without the top line. + * <br>3: DECORATION_FULL_COMPATIBLE: basic template with the top line; 40dp of height. + * <br>4: DECORATION_FULL_CONSTRAINED: basic template with the top line; 28dp of height. + * <p>See {@link android.app.Notification.DevFlags} for more details. + * @hide + */ + public static final String DECORATED_CUSTOM_VIEW_NOTIF_DECORATION = + "decorated_custom_view_notif_decoration"; + + /** * Block untrusted touches mode. * * Can be one of: diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index b34c2dceeee8..aeeaa97e2287 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -26,6 +26,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.Service; +import android.app.assist.AssistStructure.ViewNode; +import android.app.assist.AssistStructure.ViewNodeParcelable; import android.content.ComponentName; import android.content.Intent; import android.graphics.Rect; @@ -415,6 +417,8 @@ public abstract class AugmentedAutofillService extends Service { @GuardedBy("mLock") private AutofillValue mFocusedValue; @GuardedBy("mLock") + private ViewNode mFocusedViewNode; + @GuardedBy("mLock") private IFillCallback mCallback; /** @@ -532,6 +536,7 @@ public abstract class AugmentedAutofillService extends Service { synchronized (mLock) { mFocusedId = focusedId; mFocusedValue = focusedValue; + mFocusedViewNode = null; if (mCallback != null) { try { if (!mCallback.isCompleted()) { @@ -570,6 +575,25 @@ public abstract class AugmentedAutofillService extends Service { } } + @Nullable + public ViewNode getFocusedViewNode() { + synchronized (mLock) { + if (mFocusedViewNode == null) { + try { + final ViewNodeParcelable viewNodeParcelable = mClient.getViewNodeParcelable( + mFocusedId); + if (viewNodeParcelable != null) { + mFocusedViewNode = viewNodeParcelable.getViewNode(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error getting the ViewNode of the focused view: " + e); + return null; + } + } + return mFocusedViewNode; + } + } + void logEvent(@ReportEvent int event) { if (sVerbose) Log.v(TAG, "returnAndLogResult(): " + event); long duration = -1; diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java index 6927cf6541e0..f7f721a60aa0 100644 --- a/core/java/android/service/autofill/augmented/FillRequest.java +++ b/core/java/android/service/autofill/augmented/FillRequest.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.app.assist.AssistStructure.ViewNode; import android.content.ComponentName; import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy; import android.view.autofill.AutofillId; @@ -81,6 +82,14 @@ public final class FillRequest { } /** + * Gets the current {@link ViewNode} information of the field that triggered the request. + */ + @Nullable + public ViewNode getFocusedViewNode() { + return mProxy.getFocusedViewNode(); + } + + /** * Gets the Smart Suggestions object used to embed the autofill UI. * * @return object used to embed the autofill UI, or {@code null} if not supported. @@ -98,7 +107,7 @@ public final class FillRequest { - // Code below generated by codegen v1.0.14. + // Code below generated by codegen v1.0.22. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -151,10 +160,10 @@ public final class FillRequest { } @DataClass.Generated( - time = 1577399314707L, - codegenVersion = "1.0.14", + time = 1608160139217L, + codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/service/autofill/augmented/FillRequest.java", - inputSignatures = "private final @android.annotation.NonNull android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy mProxy\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\npublic int getTaskId()\npublic @android.annotation.NonNull android.content.ComponentName getActivityComponent()\npublic @android.annotation.NonNull android.view.autofill.AutofillId getFocusedId()\npublic @android.annotation.NonNull android.view.autofill.AutofillValue getFocusedValue()\npublic @android.annotation.Nullable android.service.autofill.augmented.PresentationParams getPresentationParams()\n java.lang.String proxyToString()\nclass FillRequest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genBuilder=false, genHiddenConstructor=true)") + inputSignatures = "private final @android.annotation.NonNull android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy mProxy\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\npublic int getTaskId()\npublic @android.annotation.NonNull android.content.ComponentName getActivityComponent()\npublic @android.annotation.NonNull android.view.autofill.AutofillId getFocusedId()\npublic @android.annotation.NonNull android.view.autofill.AutofillValue getFocusedValue()\npublic @android.annotation.Nullable android.app.assist.AssistStructure.ViewNode getFocusedViewNode()\npublic @android.annotation.Nullable android.service.autofill.augmented.PresentationParams getPresentationParams()\n java.lang.String proxyToString()\nclass FillRequest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genBuilder=false, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/notification/NotificationListenerFilter.java b/core/java/android/service/notification/NotificationListenerFilter.java index c945c2d64519..6fdfaabb009b 100644 --- a/core/java/android/service/notification/NotificationListenerFilter.java +++ b/core/java/android/service/notification/NotificationListenerFilter.java @@ -17,6 +17,7 @@ package android.service.notification; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; +import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT; import android.os.Parcel; @@ -35,7 +36,8 @@ public class NotificationListenerFilter implements Parcelable { public NotificationListenerFilter() { mAllowedNotificationTypes = FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING - | FLAG_FILTER_TYPE_SILENT; + | FLAG_FILTER_TYPE_SILENT + | FLAG_FILTER_TYPE_ONGOING; mDisallowedPackages = new ArraySet<>(); } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index ccde0bcf71c3..f79b59fe5432 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -242,6 +242,16 @@ public abstract class NotificationListenerService extends Service { public @interface NotificationCancelReason{}; /** + * @hide + */ + @IntDef(flag = true, prefix = { "FLAG_FILTER_TYPE_" }, value = { + FLAG_FILTER_TYPE_CONVERSATIONS, + FLAG_FILTER_TYPE_ALERTING, + FLAG_FILTER_TYPE_SILENT, + FLAG_FILTER_TYPE_ONGOING + }) + public @interface NotificationFilterTypes {} + /** * A flag value indicating that this notification listener can see conversation type * notifications. * @hide @@ -257,6 +267,12 @@ public abstract class NotificationListenerService extends Service { * @hide */ public static final int FLAG_FILTER_TYPE_SILENT = 4; + /** + * A flag value indicating that this notification listener can see important + * ( > {@link NotificationManager#IMPORTANCE_MIN}) ongoing type notifications. + * @hide + */ + public static final int FLAG_FILTER_TYPE_ONGOING = 8; /** * The full trim of the StatusBarNotification including all its features. diff --git a/core/java/android/service/translation/ITranslationCallback.aidl b/core/java/android/service/translation/ITranslationCallback.aidl new file mode 100644 index 000000000000..333cb577f790 --- /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 000000000000..6d6f2782ef4b --- /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 000000000000..a1e663aa8ff7 --- /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 000000000000..345c69c0935d --- /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 000000000000..9a2d4157696e --- /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 000000000000..b8afd7049a82 --- /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 000000000000..b0288076376c --- /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 000000000000..18cc29d12b5f --- /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/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 46a5caf176b7..e934fa483ebe 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -22,9 +22,9 @@ import static android.Manifest.permission.RECORD_AUDIO; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityThread; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; @@ -41,6 +41,7 @@ import android.media.AudioFormat; import android.media.permission.Identity; import android.os.AsyncTask; import android.os.Binder; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -78,6 +79,7 @@ public class AlwaysOnHotwordDetector { * No further interaction should be performed with the detector that returns this availability. */ public static final int STATE_HARDWARE_UNAVAILABLE = -2; + /** * Indicates that recognition for the given keyphrase is not supported. * No further interaction should be performed with the detector that returns this availability. @@ -88,11 +90,13 @@ public class AlwaysOnHotwordDetector { */ @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; + /** * Indicates that the given keyphrase is not enrolled. * The caller may choose to begin an enrollment flow for the keyphrase. */ public static final int STATE_KEYPHRASE_UNENROLLED = 1; + /** * Indicates that the given keyphrase is currently enrolled and it's possible to start * recognition for it. @@ -100,6 +104,14 @@ public class AlwaysOnHotwordDetector { public static final int STATE_KEYPHRASE_ENROLLED = 2; /** + * Indicates that the availability state of the active keyphrase can't be known due to an error. + * + * <p>NOTE: No further interaction should be performed with the detector that returns this + * state, it would be better to create {@link AlwaysOnHotwordDetector} again. + */ + public static final int STATE_ERROR = 3; + + /** * Indicates that the detector isn't ready currently. */ private static final int STATE_NOT_READY = 0; @@ -122,11 +134,13 @@ public class AlwaysOnHotwordDetector { * @hide */ public static final int RECOGNITION_FLAG_NONE = 0; + /** * Recognition flag for {@link #startRecognition(int)} that indicates * whether the trigger audio for hotword needs to be captured. */ public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 0x1; + /** * Recognition flag for {@link #startRecognition(int)} that indicates * whether the recognition should keep going on even after the keyphrase triggers. @@ -174,6 +188,7 @@ public class AlwaysOnHotwordDetector { */ public static final int RECOGNITION_MODE_VOICE_TRIGGER = SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER; + /** * User identification performed with the keyphrase recognition. * Returned by {@link #getSupportedRecognitionModes()} @@ -249,6 +264,7 @@ public class AlwaysOnHotwordDetector { private final Object mLock = new Object(); private final Handler mHandler; private final IBinder mBinder = new Binder(); + private final int mTargetSdkVersion; private int mAvailability = STATE_NOT_READY; @@ -401,8 +417,10 @@ public class AlwaysOnHotwordDetector { * @see AlwaysOnHotwordDetector#STATE_HARDWARE_UNAVAILABLE * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_UNENROLLED * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_ENROLLED + * @see AlwaysOnHotwordDetector#STATE_ERROR */ public abstract void onAvailabilityChanged(int status); + /** * Called when the keyphrase is spoken. * This implicitly stops listening for the keyphrase once it's detected. @@ -414,16 +432,19 @@ public class AlwaysOnHotwordDetector { * {@link AlwaysOnHotwordDetector#startRecognition(int)}. */ public abstract void onDetected(@NonNull EventPayload eventPayload); + /** * Called when the detection fails due to an error. */ public abstract void onError(); + /** * Called when the recognition is paused temporarily for some reason. * This is an informational callback, and the clients shouldn't be doing anything here * except showing an indication on their UI if they have to. */ public abstract void onRecognitionPaused(); + /** * Called when the recognition is resumed after it was temporarily paused. * This is an informational callback, and the clients shouldn't be doing anything here @@ -437,11 +458,12 @@ public class AlwaysOnHotwordDetector { * @param locale The java locale for the detector. * @param callback A non-null Callback for receiving the recognition events. * @param modelManagementService A service that allows management of sound models. + * @param targetSdkVersion The target SDK version. * @hide */ public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback, KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, - IVoiceInteractionManagerService modelManagementService) { + IVoiceInteractionManagerService modelManagementService, int targetSdkVersion) { mText = text; mLocale = locale; mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; @@ -449,6 +471,7 @@ public class AlwaysOnHotwordDetector { mHandler = new MyHandler(); mInternalCallback = new SoundTriggerListener(mHandler); mModelManagementService = modelManagementService; + mTargetSdkVersion = targetSdkVersion; try { Identity identity = new Identity(); identity.packageName = ActivityThread.currentOpPackageName(); @@ -469,7 +492,7 @@ public class AlwaysOnHotwordDetector { * @throws UnsupportedOperationException if the keyphrase itself isn't supported. * Callers should only call this method after a supported state callback on * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. - * @throws IllegalStateException if the detector is in an invalid state. + * @throws IllegalStateException if the detector is in an invalid or error state. * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ @@ -481,9 +504,9 @@ public class AlwaysOnHotwordDetector { } private int getSupportedRecognitionModesLocked() { - if (mAvailability == STATE_INVALID) { + if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { throw new IllegalStateException( - "getSupportedRecognitionModes called on an invalid detector"); + "getSupportedRecognitionModes called on an invalid detector or error state"); } // This method only makes sense if we can actually support a recognition. @@ -541,7 +564,7 @@ public class AlwaysOnHotwordDetector { * @throws UnsupportedOperationException if the recognition isn't supported. * Callers should only call this method after a supported state callback on * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. - * @throws IllegalStateException if the detector is in an invalid state. + * @throws IllegalStateException if the detector is in an invalid or error state. * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ @@ -549,8 +572,9 @@ public class AlwaysOnHotwordDetector { public boolean startRecognition(@RecognitionFlags int recognitionFlags) { if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")"); synchronized (mLock) { - if (mAvailability == STATE_INVALID) { - throw new IllegalStateException("startRecognition called on an invalid detector"); + if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { + throw new IllegalStateException( + "startRecognition called on an invalid detector or error state"); } // Check if we can start/stop a recognition. @@ -572,7 +596,7 @@ public class AlwaysOnHotwordDetector { * @throws UnsupportedOperationException if the recognition isn't supported. * Callers should only call this method after a supported state callback on * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. - * @throws IllegalStateException if the detector is in an invalid state. + * @throws IllegalStateException if the detector is in an invalid or error state. * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ @@ -580,8 +604,9 @@ public class AlwaysOnHotwordDetector { public boolean stopRecognition() { if (DBG) Slog.d(TAG, "stopRecognition()"); synchronized (mLock) { - if (mAvailability == STATE_INVALID) { - throw new IllegalStateException("stopRecognition called on an invalid detector"); + if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { + throw new IllegalStateException( + "stopRecognition called on an invalid detector or error state"); } // Check if we can start/stop a recognition. @@ -610,6 +635,9 @@ public class AlwaysOnHotwordDetector { * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or * if API is not supported by HAL + * @throws IllegalStateException if the detector is in an invalid or error state. + * This may happen if another detector has been instantiated or the + * {@link VoiceInteractionService} hosting this detector has been shut down. */ @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) public int setParameter(@ModelParams int modelParam, int value) { @@ -618,8 +646,9 @@ public class AlwaysOnHotwordDetector { } synchronized (mLock) { - if (mAvailability == STATE_INVALID) { - throw new IllegalStateException("setParameter called on an invalid detector"); + if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { + throw new IllegalStateException( + "setParameter called on an invalid detector or error state"); } return setParameterLocked(modelParam, value); @@ -638,6 +667,9 @@ public class AlwaysOnHotwordDetector { * * @param modelParam {@link ModelParams} * @return value of parameter + * @throws IllegalStateException if the detector is in an invalid or error state. + * This may happen if another detector has been instantiated or the + * {@link VoiceInteractionService} hosting this detector has been shut down. */ @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) public int getParameter(@ModelParams int modelParam) { @@ -646,8 +678,9 @@ public class AlwaysOnHotwordDetector { } synchronized (mLock) { - if (mAvailability == STATE_INVALID) { - throw new IllegalStateException("getParameter called on an invalid detector"); + if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { + throw new IllegalStateException( + "getParameter called on an invalid detector or error state"); } return getParameterLocked(modelParam); @@ -663,6 +696,9 @@ public class AlwaysOnHotwordDetector { * * @param modelParam {@link ModelParams} * @return supported range of parameter, null if not supported + * @throws IllegalStateException if the detector is in an invalid or error state. + * This may happen if another detector has been instantiated or the + * {@link VoiceInteractionService} hosting this detector has been shut down. */ @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) @Nullable @@ -672,8 +708,9 @@ public class AlwaysOnHotwordDetector { } synchronized (mLock) { - if (mAvailability == STATE_INVALID) { - throw new IllegalStateException("queryParameter called on an invalid detector"); + if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { + throw new IllegalStateException( + "queryParameter called on an invalid detector or error state"); } return queryParameterLocked(modelParam); @@ -735,7 +772,7 @@ public class AlwaysOnHotwordDetector { * @throws UnsupportedOperationException if managing they keyphrase isn't supported. * Callers should only call this method after a supported state callback on * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. - * @throws IllegalStateException if the detector is in an invalid state. + * @throws IllegalStateException if the detector is in an invalid or error state. * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ @@ -748,8 +785,9 @@ public class AlwaysOnHotwordDetector { } private Intent getManageIntentLocked(@KeyphraseEnrollmentInfo.ManageActions int action) { - if (mAvailability == STATE_INVALID) { - throw new IllegalStateException("getManageIntent called on an invalid detector"); + if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { + throw new IllegalStateException( + "getManageIntent called on an invalid detector or error state"); } // This method only makes sense if we can actually support a recognition. @@ -783,8 +821,10 @@ public class AlwaysOnHotwordDetector { void onSoundModelsChanged() { synchronized (mLock) { if (mAvailability == STATE_INVALID - || mAvailability == STATE_HARDWARE_UNAVAILABLE) { - Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config"); + || mAvailability == STATE_HARDWARE_UNAVAILABLE + || mAvailability == STATE_ERROR) { + Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config" + + " or in the error state"); return; } @@ -794,7 +834,16 @@ public class AlwaysOnHotwordDetector { // The availability change callback should ensure that the client starts recognition // again if needed. if (mAvailability == STATE_KEYPHRASE_ENROLLED) { - stopRecognitionLocked(); + try { + stopRecognitionLocked(); + } catch (SecurityException e) { + Slog.w(TAG, "Failed to Stop the recognition", e); + if (mTargetSdkVersion <= Build.VERSION_CODES.R) { + throw e; + } + updateAndNotifyStateChangedLocked(STATE_ERROR); + return; + } } // Execute a refresh availability task - which should then notify of a change. @@ -890,6 +939,15 @@ public class AlwaysOnHotwordDetector { } } + private void updateAndNotifyStateChangedLocked(int availability) { + if (DBG) { + Slog.d(TAG, "Hotword availability changed from " + mAvailability + + " -> " + availability); + } + mAvailability = availability; + notifyStateChangedLocked(); + } + private void notifyStateChangedLocked() { Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED); message.arg1 = mAvailability; @@ -976,25 +1034,30 @@ public class AlwaysOnHotwordDetector { @Override public Void doInBackground(Void... params) { - int availability = internalGetInitialAvailability(); - - synchronized (mLock) { - if (availability == STATE_NOT_READY) { - internalUpdateEnrolledKeyphraseMetadata(); - if (mKeyphraseMetadata != null) { - availability = STATE_KEYPHRASE_ENROLLED; - } else { - availability = STATE_KEYPHRASE_UNENROLLED; + try { + int availability = internalGetInitialAvailability(); + + synchronized (mLock) { + if (availability == STATE_NOT_READY) { + internalUpdateEnrolledKeyphraseMetadata(); + if (mKeyphraseMetadata != null) { + availability = STATE_KEYPHRASE_ENROLLED; + } else { + availability = STATE_KEYPHRASE_UNENROLLED; + } } + updateAndNotifyStateChangedLocked(availability); } - - if (DBG) { - Slog.d(TAG, "Hotword availability changed from " + mAvailability - + " -> " + availability); + } catch (SecurityException e) { + Slog.w(TAG, "Failed to refresh availability", e); + if (mTargetSdkVersion <= Build.VERSION_CODES.R) { + throw e; + } + synchronized (mLock) { + updateAndNotifyStateChangedLocked(STATE_ERROR); } - mAvailability = availability; - notifyStateChangedLocked(); } + return null; } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index fb03ed45113e..4aff695f1b47 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -325,7 +325,8 @@ public class VoiceInteractionService extends Service { // Allow only one concurrent recognition via the APIs. safelyShutdownHotwordDetector(); mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback, - mKeyphraseEnrollmentInfo, mSystemService); + mKeyphraseEnrollmentInfo, mSystemService, + getApplicationContext().getApplicationInfo().targetSdkVersion); } return mHotwordDetector; } diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index ac6208b1714c..021d5fc4e526 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -38,12 +38,11 @@ import android.telephony.Annotation.PreciseDisconnectCauses; import android.telephony.Annotation.RadioPowerState; import android.telephony.Annotation.SimActivationState; import android.telephony.Annotation.SrvccState; -import android.telephony.emergency.EmergencyNumber; -import android.telephony.ims.ImsReasonInfo; import android.telephony.NetworkRegistrationInfo.Domain; import android.telephony.TelephonyManager.DataEnabledReason; import android.telephony.TelephonyManager.DataState; -import android.util.Log; +import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsReasonInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IPhoneStateListener; @@ -1082,7 +1081,9 @@ public class PhoneStateListener { /** * Create a PhoneStateListener for the Phone with the default subscription. - * This class requires Looper.myLooper() not return null. + * If this is created for use with deprecated API + * {@link TelephonyManager#listen(PhoneStateListener, int)}, then this class requires + * Looper.myLooper() not return null. */ public PhoneStateListener() { this(null, Looper.myLooper()); @@ -1120,7 +1121,10 @@ public class PhoneStateListener { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public PhoneStateListener(Integer subId, Looper looper) { - this(subId, new HandlerExecutor(new Handler(looper))); + if (looper != null) { + setExecutor(new HandlerExecutor(new Handler(looper))); + } + mSubId = subId; if (subId != null && VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q) { throw new IllegalArgumentException("PhoneStateListener with subId: " @@ -1140,7 +1144,8 @@ public class PhoneStateListener { */ @Deprecated public PhoneStateListener(@NonNull Executor executor) { - this(null, executor); + setExecutor(executor); + mSubId = null; } private @NonNull Executor mExecutor; @@ -1153,12 +1158,14 @@ public class PhoneStateListener { throw new IllegalArgumentException("PhoneStateListener Executor must be non-null"); } mExecutor = executor; + callback = new IPhoneStateListenerStub(this, mExecutor); } - private PhoneStateListener(Integer subId, Executor executor) { - setExecutor(executor); - mSubId = subId; - callback = new IPhoneStateListenerStub(this, mExecutor); + /** + * @hide + */ + public boolean isExecutorSet() { + return mExecutor != null; } /** diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java index dae760f989f6..86120d1e650c 100644 --- a/core/java/android/util/SparseArray.java +++ b/core/java/android/util/SparseArray.java @@ -241,6 +241,14 @@ public class SparseArray<E> implements Cloneable { } /** + * 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/util/TypedValue.java b/core/java/android/util/TypedValue.java index 7f1ee302903b..19de396c4a4a 100644 --- a/core/java/android/util/TypedValue.java +++ b/core/java/android/util/TypedValue.java @@ -17,8 +17,14 @@ package android.util; import android.annotation.AnyRes; +import android.annotation.FloatRange; +import android.annotation.IntDef; +import android.annotation.IntRange; import android.content.pm.ActivityInfo.Config; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Container for a dynamically typed data value. Primarily used with * {@link android.content.res.Resources} for holding resource values. @@ -95,6 +101,18 @@ public class TypedValue { * defined below. */ public static final int COMPLEX_UNIT_MASK = 0xf; + /** @hide **/ + @IntDef(prefix = "COMPLEX_UNIT_", value = { + COMPLEX_UNIT_PX, + COMPLEX_UNIT_DIP, + COMPLEX_UNIT_SP, + COMPLEX_UNIT_PT, + COMPLEX_UNIT_IN, + COMPLEX_UNIT_MM, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ComplexDimensionUnit {} + /** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */ public static final int COMPLEX_UNIT_PX = 0; /** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent @@ -381,7 +399,7 @@ public class TypedValue { * @return The complex floating point value multiplied by the appropriate * metrics depending on its unit. */ - public static float applyDimension(int unit, float value, + public static float applyDimension(@ComplexDimensionUnit int unit, float value, DisplayMetrics metrics) { switch (unit) { @@ -417,6 +435,130 @@ public class TypedValue { } /** + * Construct a complex data integer. This validates the radix and the magnitude of the + * mantissa, and sets the {@link TypedValue#COMPLEX_MANTISSA_MASK} and + * {@link TypedValue#COMPLEX_RADIX_MASK} components as provided. The units are not set. + ** + * @param mantissa an integer representing the mantissa. + * @param radix a radix option, e.g. {@link TypedValue#COMPLEX_RADIX_23p0}. + * @return A complex data integer representing the value. + * @hide + */ + private static int createComplex(@IntRange(from = -0x800000, to = 0x7FFFFF) int mantissa, + int radix) { + if (mantissa < -0x800000 || mantissa >= 0x800000) { + throw new IllegalArgumentException("Magnitude of mantissa is too large: " + mantissa); + } + if (radix < TypedValue.COMPLEX_RADIX_23p0 || radix > TypedValue.COMPLEX_RADIX_0p23) { + throw new IllegalArgumentException("Invalid radix: " + radix); + } + return ((mantissa & TypedValue.COMPLEX_MANTISSA_MASK) << TypedValue.COMPLEX_MANTISSA_SHIFT) + | (radix << TypedValue.COMPLEX_RADIX_SHIFT); + } + + /** + * Convert a base value to a complex data integer. This sets the {@link + * TypedValue#COMPLEX_MANTISSA_MASK} and {@link TypedValue#COMPLEX_RADIX_MASK} fields of the + * data to create a floating point representation of the given value. The units are not set. + * + * <p>This is the inverse of {@link TypedValue#complexToFloat(int)}. + * + * @param value An integer value. + * @return A complex data integer representing the value. + * @hide + */ + public static int intToComplex(int value) { + if (value < -0x800000 || value >= 0x800000) { + throw new IllegalArgumentException("Magnitude of the value is too large: " + value); + } + return createComplex(value, TypedValue.COMPLEX_RADIX_23p0); + } + + /** + * Convert a base value to a complex data integer. This sets the {@link + * TypedValue#COMPLEX_MANTISSA_MASK} and {@link TypedValue#COMPLEX_RADIX_MASK} fields of the + * data to create a floating point representation of the given value. The units are not set. + * + * <p>This is the inverse of {@link TypedValue#complexToFloat(int)}. + * + * @param value A floating point value. + * @return A complex data integer representing the value. + * @hide + */ + public static int floatToComplex(@FloatRange(from = -0x800000, to = 0x7FFFFF) float value) { + // validate that the magnitude fits in this representation + if (value < (float) -0x800000 - .5f || value >= (float) 0x800000 - .5f) { + throw new IllegalArgumentException("Magnitude of the value is too large: " + value); + } + try { + // If there's no fraction, use integer representation, as that's clearer + if (value == (float) (int) value) { + return createComplex((int) value, TypedValue.COMPLEX_RADIX_23p0); + } + float absValue = Math.abs(value); + // If the magnitude is 0, we don't need any magnitude digits + if (absValue < 1f) { + return createComplex(Math.round(value * (1 << 23)), TypedValue.COMPLEX_RADIX_0p23); + } + // If the magnitude is less than 2^8, use 8 magnitude digits + if (absValue < (float) (1 << 8)) { + return createComplex(Math.round(value * (1 << 15)), TypedValue.COMPLEX_RADIX_8p15); + } + // If the magnitude is less than 2^16, use 16 magnitude digits + if (absValue < (float) (1 << 16)) { + return createComplex(Math.round(value * (1 << 7)), TypedValue.COMPLEX_RADIX_16p7); + } + // The magnitude requires all 23 digits + return createComplex(Math.round(value), TypedValue.COMPLEX_RADIX_23p0); + } catch (IllegalArgumentException ex) { + // Wrap exception so as to include the value argument in the message. + throw new IllegalArgumentException("Unable to convert value to complex: " + value, ex); + } + } + + /** + * <p>Creates a complex data integer that stores a dimension value and units. + * + * <p>The resulting value can be passed to e.g. + * {@link TypedValue#complexToDimensionPixelOffset(int, DisplayMetrics)} to calculate the pixel + * value for the dimension. + * + * @param value the value of the dimension + * @param units the units of the dimension, e.g. {@link TypedValue#COMPLEX_UNIT_DIP} + * @return A complex data integer representing the value and units of the dimension. + * @hide + */ + public static int createComplexDimension( + @IntRange(from = -0x800000, to = 0x7FFFFF) int value, + @ComplexDimensionUnit int units) { + if (units < TypedValue.COMPLEX_UNIT_PX || units > TypedValue.COMPLEX_UNIT_MM) { + throw new IllegalArgumentException("Must be a valid COMPLEX_UNIT_*: " + units); + } + return intToComplex(value) | units; + } + + /** + * <p>Creates a complex data integer that stores a dimension value and units. + * + * <p>The resulting value can be passed to e.g. + * {@link TypedValue#complexToDimensionPixelOffset(int, DisplayMetrics)} to calculate the pixel + * value for the dimension. + * + * @param value the value of the dimension + * @param units the units of the dimension, e.g. {@link TypedValue#COMPLEX_UNIT_DIP} + * @return A complex data integer representing the value and units of the dimension. + * @hide + */ + public static int createComplexDimension( + @FloatRange(from = -0x800000, to = 0x7FFFFF) float value, + @ComplexDimensionUnit int units) { + if (units < TypedValue.COMPLEX_UNIT_PX || units > TypedValue.COMPLEX_UNIT_MM) { + throw new IllegalArgumentException("Must be a valid COMPLEX_UNIT_*: " + units); + } + return floatToComplex(value) | units; + } + + /** * Converts a complex data value holding a fraction to its final floating * point value. The given <var>data</var> must be structured as a * {@link #TYPE_FRACTION}. diff --git a/core/java/android/uwb/IUwbAdapter.aidl b/core/java/android/uwb/IUwbAdapter.aidl index 2c8b2e462510..b9c55081a103 100644 --- a/core/java/android/uwb/IUwbAdapter.aidl +++ b/core/java/android/uwb/IUwbAdapter.aidl @@ -119,42 +119,96 @@ interface IUwbAdapter { PersistableBundle getSpecificationInfo(); /** - * Request to start a new ranging session + * Request to open a new ranging session * - * This function must return before calling IUwbAdapterCallbacks - * #onRangingStarted, #onRangingClosed, or #onRangingResult. + * This function must return before calling any functions in + * IUwbAdapterCallbacks. * - * A ranging session does not need to be started before returning. + * This function does not start the ranging session, but all necessary + * components must be initialized and ready to start a new ranging + * session prior to calling IUwbAdapterCallback#onRangingOpened. * - * IUwbAdapterCallbacks#onRangingStarted must be called within - * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being called - * if the ranging session is scheduled to start successfully. + * IUwbAdapterCallbacks#onRangingOpened must be called within + * RANGING_SESSION_OPEN_THRESHOLD_MS milliseconds of #openRanging being + * called if the ranging session is opened successfully. * - * IUwbAdapterCallbacks#onRangingStartFailed must be called within - * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being called - * if the ranging session fails to be scheduled to start successfully. + * IUwbAdapterCallbacks#onRangingOpenFailed must be called within + * RANGING_SESSION_OPEN_THRESHOLD_MS milliseconds of #openRanging being called + * if the ranging session fails to be opened. * * @param rangingCallbacks the callbacks used to deliver ranging information * @param parameters the configuration to use for ranging * @return a SessionHandle used to identify this ranging request */ - SessionHandle startRanging(in IUwbRangingCallbacks rangingCallbacks, - in PersistableBundle parameters); + SessionHandle openRanging(in IUwbRangingCallbacks rangingCallbacks, + in PersistableBundle parameters); + + /** + * Request to start ranging + * + * IUwbAdapterCallbacks#onRangingStarted must be called within + * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being + * called if the ranging session starts successfully. + * + * IUwbAdapterCallbacks#onRangingStartFailed must be called within + * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being + * called if the ranging session fails to be started. + * + * @param sessionHandle the session handle to start ranging for + * @param parameters additional configuration required to start ranging + */ + void startRanging(in SessionHandle sessionHandle, + in PersistableBundle parameters); + + /** + * Request to reconfigure ranging + * + * IUwbAdapterCallbacks#onRangingReconfigured must be called after + * successfully reconfiguring the session. + * + * IUwbAdapterCallbacks#onRangingReconfigureFailed must be called after + * failing to reconfigure the session. + * + * A session must not be modified by a failed call to #reconfigureRanging. + * + * @param sessionHandle the session handle to start ranging for + * @param parameters the parameters to reconfigure and their new values + */ + void reconfigureRanging(in SessionHandle sessionHandle, + in PersistableBundle parameters); + + /** + * Request to stop ranging + * + * IUwbAdapterCallbacks#onRangingStopped must be called after + * successfully stopping the session. + * + * IUwbAdapterCallbacks#onRangingStopFailed must be called after failing + * to stop the session. + * + * @param sessionHandle the session handle to stop ranging for + */ + void stopRanging(in SessionHandle sessionHandle); /** - * Stop and close ranging for the session associated with the given handle + * Close ranging for the session associated with the given handle * * Calling with an invalid handle or a handle that has already been closed * is a no-op. * * IUwbAdapterCallbacks#onRangingClosed must be called within - * RANGING_SESSION_CLOSE_THRESHOLD_MS of #stopRanging being called. + * RANGING_SESSION_CLOSE_THRESHOLD_MS of #closeRanging being called. * - * @param sessionHandle the session handle to stop ranging for + * @param sessionHandle the session handle to close ranging for */ void closeRanging(in SessionHandle sessionHandle); /** + * The maximum allowed time to open a ranging session. + */ + const int RANGING_SESSION_OPEN_THRESHOLD_MS = 3000; // Value TBD + + /** * The maximum allowed time to start a ranging session. */ const int RANGING_SESSION_START_THRESHOLD_MS = 3000; // Value TBD diff --git a/core/java/android/uwb/IUwbRangingCallbacks.aidl b/core/java/android/uwb/IUwbRangingCallbacks.aidl index 1fc3bfd818c3..f71f3ff7ad44 100644 --- a/core/java/android/uwb/IUwbRangingCallbacks.aidl +++ b/core/java/android/uwb/IUwbRangingCallbacks.aidl @@ -17,16 +17,33 @@ package android.uwb; import android.os.PersistableBundle; -import android.uwb.CloseReason; +import android.uwb.RangingChangeReason; import android.uwb.RangingReport; import android.uwb.SessionHandle; -import android.uwb.StartFailureReason; /** * @hide */ interface IUwbRangingCallbacks { /** + * Called when the ranging session has been opened + * + * @param sessionHandle the session the callback is being invoked for + */ + void onRangingOpened(in SessionHandle sessionHandle); + + /** + * Called when a ranging session fails to start + * + * @param sessionHandle the session the callback is being invoked for + * @param reason the reason the session failed to start + * @param parameters protocol specific parameters + */ + void onRangingOpenFailed(in SessionHandle sessionHandle, + RangingChangeReason reason, + in PersistableBundle parameters); + + /** * Called when ranging has started * * May output parameters generated by the lower layers that must be sent to the @@ -47,8 +64,49 @@ interface IUwbRangingCallbacks { * @param reason the reason the session failed to start * @param parameters protocol specific parameters */ - void onRangingStartFailed(in SessionHandle sessionHandle, StartFailureReason reason, + void onRangingStartFailed(in SessionHandle sessionHandle, + RangingChangeReason reason, in PersistableBundle parameters); + + /** + * Called when ranging has been reconfigured + * + * @param sessionHandle the session the callback is being invoked for + * @param parameters the updated ranging configuration + */ + void onRangingReconfigured(in SessionHandle sessionHandle, + in PersistableBundle parameters); + + /** + * Called when a ranging session fails to be reconfigured + * + * @param sessionHandle the session the callback is being invoked for + * @param reason the reason the session failed to reconfigure + * @param parameters protocol specific parameters + */ + void onRangingReconfigureFailed(in SessionHandle sessionHandle, + RangingChangeReason reason, + in PersistableBundle parameters); + + /** + * Called when the ranging session has been stopped + * + * @param sessionHandle the session the callback is being invoked for + */ + + void onRangingStopped(in SessionHandle sessionHandle); + + /** + * Called when a ranging session fails to stop + * + * @param sessionHandle the session the callback is being invoked for + * @param reason the reason the session failed to stop + * @param parameters protocol specific parameters + */ + void onRangingStopFailed(in SessionHandle sessionHandle, + RangingChangeReason reason, + in PersistableBundle parameters); + /** * Called when a ranging session is closed * @@ -56,7 +114,8 @@ interface IUwbRangingCallbacks { * @param reason the reason the session was closed * @param parameters protocol specific parameters */ - void onRangingClosed(in SessionHandle sessionHandle, CloseReason reason, + void onRangingClosed(in SessionHandle sessionHandle, + RangingChangeReason reason, in PersistableBundle parameters); /** diff --git a/core/java/android/uwb/CloseReason.aidl b/core/java/android/uwb/RangingChangeReason.aidl index bef129e2c1c7..19d4b3949d07 100644 --- a/core/java/android/uwb/CloseReason.aidl +++ b/core/java/android/uwb/RangingChangeReason.aidl @@ -20,39 +20,44 @@ package android.uwb; * @hide */ @Backing(type="int") -enum CloseReason { +enum RangingChangeReason { /** * Unknown reason */ UNKNOWN, /** - * A local API call triggered the close, such as a call to - * IUwbAdapter.stopRanging. + * A local API call triggered the change, such as a call to + * IUwbAdapter.closeRanging. */ LOCAL_API, /** - * The maximum number of sessions has been reached. This error may be generated - * for an active session if a higher priority session begins. + * The maximum number of sessions has been reached. This may be generated for + * an active session if a higher priority session begins. */ MAX_SESSIONS_REACHED, /** - * The system state has changed resulting in the session ending (e.g. the user - * disables UWB, or the user's locale changes and an active channel is no longer - * permitted to be used). + * The system state has changed resulting in the session changing (e.g. the + * user disables UWB, or the user's locale changes and an active channel is no + * longer permitted to be used). */ SYSTEM_POLICY, /** - * The remote device has requested to terminate the session + * The remote device has requested to change the session */ REMOTE_REQUEST, /** - * The session was closed for a protocol specific reason + * The session changed for a protocol specific reason */ PROTOCOL_SPECIFIC, + + /** + * The provided parameters were invalid + */ + BAD_PARAMETERS, } diff --git a/core/java/android/uwb/RangingManager.java b/core/java/android/uwb/RangingManager.java index a9bf4abe566a..5ac95d49c1bb 100644 --- a/core/java/android/uwb/RangingManager.java +++ b/core/java/android/uwb/RangingManager.java @@ -50,7 +50,7 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { @NonNull RangingSession.Callback callbacks) { SessionHandle sessionHandle; try { - sessionHandle = mAdapter.startRanging(this, params); + sessionHandle = mAdapter.openRanging(this, params); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -59,7 +59,7 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { if (hasSession(sessionHandle)) { Log.w(TAG, "Newly created session unexpectedly reuses an active SessionHandle"); executor.execute(() -> callbacks.onClosed( - RangingSession.Callback.CLOSE_REASON_LOCAL_GENERIC_ERROR, + RangingSession.Callback.REASON_GENERIC_ERROR, new PersistableBundle())); } @@ -75,6 +75,67 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { } @Override + public void onRangingOpened(SessionHandle sessionHandle) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(TAG, + "onRangingOpened - received unexpected SessionHandle: " + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingOpened(); + } + } + + @Override + public void onRangingOpenFailed(SessionHandle sessionHandle, @RangingChangeReason int reason, + PersistableBundle parameters) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(TAG, + "onRangingOpened - received unexpected SessionHandle: " + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingOpenFailed(convertToReason(reason), parameters); + mRangingSessionTable.remove(sessionHandle); + } + } + + @Override + public void onRangingReconfigured(SessionHandle sessionHandle, PersistableBundle parameters) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(TAG, + "onRangingReconfigured - received unexpected SessionHandle: " + + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingReconfigured(parameters); + } + } + + @Override + public void onRangingReconfigureFailed(SessionHandle sessionHandle, + @RangingChangeReason int reason, PersistableBundle params) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(TAG, "onRangingStartFailed - received unexpected SessionHandle: " + + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingReconfigureFailed(convertToReason(reason), params); + } + } + + + @Override public void onRangingStarted(SessionHandle sessionHandle, PersistableBundle parameters) { synchronized (this) { if (!hasSession(sessionHandle)) { @@ -89,7 +150,7 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { } @Override - public void onRangingStartFailed(SessionHandle sessionHandle, int reason, + public void onRangingStartFailed(SessionHandle sessionHandle, @RangingChangeReason int reason, PersistableBundle params) { synchronized (this) { if (!hasSession(sessionHandle)) { @@ -99,13 +160,42 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { } RangingSession session = mRangingSessionTable.get(sessionHandle); - session.onRangingClosed(convertStartFailureToCloseReason(reason), params); - mRangingSessionTable.remove(sessionHandle); + session.onRangingStartFailed(convertToReason(reason), params); + } + } + + @Override + public void onRangingStopped(SessionHandle sessionHandle) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(TAG, "onRangingStopped - received unexpected SessionHandle: " + + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingStopped(); } } @Override - public void onRangingClosed(SessionHandle sessionHandle, int reason, PersistableBundle params) { + public void onRangingStopFailed(SessionHandle sessionHandle, @RangingChangeReason int reason, + PersistableBundle parameters) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(TAG, "onRangingStopFailed - received unexpected SessionHandle: " + + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingStopFailed(convertToReason(reason), parameters); + } + } + + @Override + public void onRangingClosed(SessionHandle sessionHandle, @RangingChangeReason int reason, + PersistableBundle params) { synchronized (this) { if (!hasSession(sessionHandle)) { Log.w(TAG, "onRangingClosed - received unexpected SessionHandle: " + sessionHandle); @@ -113,7 +203,7 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { } RangingSession session = mRangingSessionTable.get(sessionHandle); - session.onRangingClosed(convertToCloseReason(reason), params); + session.onRangingClosed(convertToReason(reason), params); mRangingSessionTable.remove(sessionHandle); } } @@ -131,48 +221,30 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { } } - @RangingSession.Callback.CloseReason - private static int convertToCloseReason(@CloseReason int reason) { + @RangingSession.Callback.Reason + private static int convertToReason(@RangingChangeReason int reason) { switch (reason) { - case CloseReason.LOCAL_API: - return RangingSession.Callback.CLOSE_REASON_LOCAL_CLOSE_API; - - case CloseReason.MAX_SESSIONS_REACHED: - return RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED; - - case CloseReason.SYSTEM_POLICY: - return RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY; + case RangingChangeReason.LOCAL_API: + return RangingSession.Callback.REASON_LOCAL_REQUEST; - case CloseReason.REMOTE_REQUEST: - return RangingSession.Callback.CLOSE_REASON_REMOTE_REQUEST; + case RangingChangeReason.MAX_SESSIONS_REACHED: + return RangingSession.Callback.REASON_MAX_SESSIONS_REACHED; - case CloseReason.PROTOCOL_SPECIFIC: - return RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC; - - case CloseReason.UNKNOWN: - default: - return RangingSession.Callback.CLOSE_REASON_UNKNOWN; - } - } - - @RangingSession.Callback.CloseReason - private static int convertStartFailureToCloseReason(@StartFailureReason int reason) { - switch (reason) { - case StartFailureReason.BAD_PARAMETERS: - return RangingSession.Callback.CLOSE_REASON_LOCAL_BAD_PARAMETERS; + case RangingChangeReason.SYSTEM_POLICY: + return RangingSession.Callback.REASON_SYSTEM_POLICY; - case StartFailureReason.MAX_SESSIONS_REACHED: - return RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED; + case RangingChangeReason.REMOTE_REQUEST: + return RangingSession.Callback.REASON_REMOTE_REQUEST; - case StartFailureReason.SYSTEM_POLICY: - return RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY; + case RangingChangeReason.PROTOCOL_SPECIFIC: + return RangingSession.Callback.REASON_PROTOCOL_SPECIFIC_ERROR; - case StartFailureReason.PROTOCOL_SPECIFIC: - return RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC; + case RangingChangeReason.BAD_PARAMETERS: + return RangingSession.Callback.REASON_BAD_PARAMETERS; - case StartFailureReason.UNKNOWN: + case RangingChangeReason.UNKNOWN: default: - return RangingSession.Callback.CLOSE_REASON_UNKNOWN; + return RangingSession.Callback.REASON_UNKNOWN; } } } diff --git a/core/java/android/uwb/RangingSession.java b/core/java/android/uwb/RangingSession.java index b0dbd85c0812..0f87af415825 100644 --- a/core/java/android/uwb/RangingSession.java +++ b/core/java/android/uwb/RangingSession.java @@ -36,9 +36,9 @@ import java.util.concurrent.Executor; * <p>To get an instance of {@link RangingSession}, first use * {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} to request to open a * session. Once the session is opened, a {@link RangingSession} object is provided through - * {@link RangingSession.Callback#onOpenSuccess(RangingSession, PersistableBundle)}. If opening a - * session fails, the failure is reported through - * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} with the failure reason. + * {@link RangingSession.Callback#onOpened(RangingSession)}. If opening a session fails, the failure + * is reported through {@link RangingSession.Callback#onOpenFailed(int, PersistableBundle)} with the + * failure reason. * * @hide */ @@ -50,103 +50,162 @@ public final class RangingSession implements AutoCloseable { private final Callback mCallback; private enum State { + /** + * The state of the {@link RangingSession} until + * {@link RangingSession.Callback#onOpened(RangingSession)} is invoked + */ INIT, - OPEN, - CLOSED, + + /** + * The {@link RangingSession} is initialized and ready to begin ranging + */ + IDLE, + + /** + * The {@link RangingSession} is actively ranging + */ + ACTIVE, + + /** + * The {@link RangingSession} is closed and may not be used for ranging. + */ + CLOSED } - private State mState; + private State mState = State.INIT; /** * Interface for receiving {@link RangingSession} events */ public interface Callback { /** - * Invoked when {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} - * is successful - * - * @param session the newly opened {@link RangingSession} - * @param sessionInfo session specific parameters from lower layers - */ - void onOpenSuccess(@NonNull RangingSession session, @NonNull PersistableBundle sessionInfo); - - /** * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { - CLOSE_REASON_UNKNOWN, - CLOSE_REASON_LOCAL_CLOSE_API, - CLOSE_REASON_LOCAL_BAD_PARAMETERS, - CLOSE_REASON_LOCAL_GENERIC_ERROR, - CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED, - CLOSE_REASON_LOCAL_SYSTEM_POLICY, - CLOSE_REASON_REMOTE_GENERIC_ERROR, - CLOSE_REASON_REMOTE_REQUEST}) - @interface CloseReason {} + REASON_UNKNOWN, + REASON_LOCAL_REQUEST, + REASON_REMOTE_REQUEST, + REASON_BAD_PARAMETERS, + REASON_GENERIC_ERROR, + REASON_MAX_SESSIONS_REACHED, + REASON_SYSTEM_POLICY, + REASON_PROTOCOL_SPECIFIC_ERROR}) + @interface Reason {} /** * Indicates that the session was closed or failed to open due to an unknown reason */ - int CLOSE_REASON_UNKNOWN = 0; + int REASON_UNKNOWN = 0; /** * Indicates that the session was closed or failed to open because * {@link AutoCloseable#close()} or {@link RangingSession#close()} was called */ - int CLOSE_REASON_LOCAL_CLOSE_API = 1; + int REASON_LOCAL_REQUEST = 1; /** - * Indicates that the session failed to open due to erroneous parameters passed - * to {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} + * Indicates that the session was closed or failed to open due to an explicit request from + * the remote device. */ - int CLOSE_REASON_LOCAL_BAD_PARAMETERS = 2; + int REASON_REMOTE_REQUEST = 2; /** - * Indicates that the session was closed due to some local error on this device besides the - * error code already listed + * Indicates that the session was closed or failed to open due to erroneous parameters */ - int CLOSE_REASON_LOCAL_GENERIC_ERROR = 3; + int REASON_BAD_PARAMETERS = 3; /** - * Indicates that the session failed to open because the number of currently open sessions - * is equal to {@link UwbManager#getMaxSimultaneousSessions()} + * Indicates an error on this device besides the error code already listed */ - int CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED = 4; + int REASON_GENERIC_ERROR = 4; /** - * Indicates that the session was closed or failed to open due to local system policy, such + * Indicates that the number of currently open sessions is equal to + * {@link UwbManager#getMaxSimultaneousSessions()} and additional sessions may not be + * opened. + */ + int REASON_MAX_SESSIONS_REACHED = 5; + + /** + * Indicates that the local system policy caused the change, such * as privacy policy, power management policy, permissions, and more. */ - int CLOSE_REASON_LOCAL_SYSTEM_POLICY = 5; + int REASON_SYSTEM_POLICY = 6; /** - * Indicates that the session was closed or failed to open due to an error with the remote - * device besides error codes already listed. + * Indicates a protocol specific error. The associated {@link PersistableBundle} should be + * consulted for additional information. */ - int CLOSE_REASON_REMOTE_GENERIC_ERROR = 6; + int REASON_PROTOCOL_SPECIFIC_ERROR = 7; /** - * Indicates that the session was closed or failed to open due to an explicit request from - * the remote device. + * Invoked when {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} + * is successful + * + * @param session the newly opened {@link RangingSession} + */ + void onOpened(@NonNull RangingSession session); + + /** + * Invoked if {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}} + * fails + * + * @param reason the failure reason + * @param params protocol specific parameters + */ + void onOpenFailed(@Reason int reason, @NonNull PersistableBundle params); + + /** + * Invoked when {@link RangingSession#start(PersistableBundle)} is successful + * @param sessionInfo session specific parameters from the lower layers + */ + void onStarted(@NonNull PersistableBundle sessionInfo); + + /** + * Invoked when {@link RangingSession#start(PersistableBundle)} fails + * + * @param reason the failure reason + * @param params protocol specific parameters + */ + void onStartFailed(@Reason int reason, @NonNull PersistableBundle params); + + /** + * Invoked when a request to reconfigure the session succeeds + * + * @param params the updated ranging configuration + */ + void onReconfigured(@NonNull PersistableBundle params); + + /** + * Invoked when a request to reconfigure the session fails + * + * @param reason reason the session failed to be reconfigured + * @param params protocol specific failure reasons */ - int CLOSE_REASON_REMOTE_REQUEST = 7; + void onReconfigureFailed(@Reason int reason, @NonNull PersistableBundle params); /** - * Indicates that the session was closed for a protocol specific reason. The associated - * {@link PersistableBundle} should be consulted for additional information. + * Invoked when a request to stop the session succeeds */ - int CLOSE_REASON_PROTOCOL_SPECIFIC = 8; + void onStopped(); /** + * Invoked when a request to stop the session fails + * + * @param reason reason the session failed to be stopped + * @param params protocol specific failure reasons + */ + void onStopFailed(@Reason int reason, @NonNull PersistableBundle params); + + /** * Invoked when session is either closed spontaneously, or per user request via - * {@link RangingSession#close()} or {@link AutoCloseable#close()}, or when session failed - * to open. + * {@link RangingSession#close()} or {@link AutoCloseable#close()}. * * @param reason reason for the session closure * @param parameters protocol specific parameters related to the close reason */ - void onClosed(@CloseReason int reason, @NonNull PersistableBundle parameters); + void onClosed(@Reason int reason, @NonNull PersistableBundle parameters); /** * Called once per ranging interval even when a ranging measurement fails @@ -172,12 +231,95 @@ public final class RangingSession implements AutoCloseable { * @hide */ public boolean isOpen() { - return mState == State.OPEN; + return mState == State.IDLE || mState == State.ACTIVE; + } + + /** + * Begins ranging for the session. + * + * <p>On successfully starting a ranging session, + * {@link RangingSession.Callback#onStarted(PersistableBundle)} is invoked. + * + * <p>On failure to start the session, + * {@link RangingSession.Callback#onStartFailed(int, PersistableBundle)} is invoked. + * + * @param params configuration parameters for starting the session + */ + public void start(@NonNull PersistableBundle params) { + if (mState != State.IDLE) { + throw new IllegalStateException(); + } + + try { + mAdapter.startRanging(mSessionHandle, params); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Attempts to reconfigure the session with the given parameters + * <p>This call may be made when the session is open. + * + * <p>On successfully reconfiguring the session + * {@link RangingSession.Callback#onReconfigured(PersistableBundle)} is invoked. + * + * <p>On failure to reconfigure the session, + * {@link RangingSession.Callback#onReconfigureFailed(int, PersistableBundle)} is invoked. + * + * @param params the parameters to reconfigure and their new values + */ + public void reconfigure(@NonNull PersistableBundle params) { + if (mState != State.ACTIVE && mState != State.IDLE) { + throw new IllegalStateException(); + } + + try { + mAdapter.reconfigureRanging(mSessionHandle, params); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Stops actively ranging + * + * <p>A session that has been stopped may be resumed by calling + * {@link RangingSession#start(PersistableBundle)} without the need to open a new session. + * + * <p>Stopping a {@link RangingSession} is useful when the lower layers should not discard + * the parameters of the session, or when a session needs to be able to be resumed quickly. + * + * <p>If the {@link RangingSession} is no longer needed, use {@link RangingSession#close()} to + * completely close the session and allow lower layers of the stack to perform necessarily + * cleanup. + * + * <p>Stopped sessions may be closed by the system at any time. In such a case, + * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} is invoked. + * + * <p>On failure to stop the session, + * {@link RangingSession.Callback#onStopFailed(int, PersistableBundle)} is invoked. + */ + public void stop() { + if (mState != State.ACTIVE) { + throw new IllegalStateException(); + } + + try { + mAdapter.stopRanging(mSessionHandle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** * Close the ranging session - * <p>If this session is currently open, it will close and stop the session. + * + * <p>After calling this function, in order resume ranging, a new {@link RangingSession} must + * be opened by calling + * {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}. + * + * <p>If this session is currently ranging, it will stop and close the session. * <p>If the session is in the process of being opened, it will attempt to stop the session from * being opened. * <p>If the session is already closed, the registered @@ -192,7 +334,7 @@ public final class RangingSession implements AutoCloseable { public void close() { if (mState == State.CLOSED) { mExecutor.execute(() -> mCallback.onClosed( - Callback.CLOSE_REASON_LOCAL_CLOSE_API, new PersistableBundle())); + Callback.REASON_LOCAL_REQUEST, new PersistableBundle())); return; } @@ -206,32 +348,114 @@ public final class RangingSession implements AutoCloseable { /** * @hide */ + public void onRangingOpened() { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingOpened invoked for a closed session"); + return; + } + + mState = State.IDLE; + executeCallback(() -> mCallback.onOpened(this)); + } + + /** + * @hide + */ + public void onRangingOpenFailed(@Callback.Reason int reason, + @NonNull PersistableBundle params) { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingOpenFailed invoked for a closed session"); + return; + } + + mState = State.CLOSED; + executeCallback(() -> mCallback.onOpenFailed(reason, params)); + } + + /** + * @hide + */ public void onRangingStarted(@NonNull PersistableBundle parameters) { if (mState == State.CLOSED) { Log.w(TAG, "onRangingStarted invoked for a closed session"); return; } - mState = State.OPEN; - final long identity = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onOpenSuccess(this, parameters)); - } finally { - Binder.restoreCallingIdentity(identity); + mState = State.ACTIVE; + executeCallback(() -> mCallback.onStarted(parameters)); + } + + /** + * @hide + */ + public void onRangingStartFailed(@Callback.Reason int reason, + @NonNull PersistableBundle params) { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingStartFailed invoked for a closed session"); + return; } + + executeCallback(() -> mCallback.onStartFailed(reason, params)); } /** * @hide */ - public void onRangingClosed(@Callback.CloseReason int reason, PersistableBundle parameters) { - mState = State.CLOSED; - final long identity = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onClosed(reason, parameters)); - } finally { - Binder.restoreCallingIdentity(identity); + public void onRangingReconfigured(@NonNull PersistableBundle params) { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingReconfigured invoked for a closed session"); + return; + } + + executeCallback(() -> mCallback.onReconfigured(params)); + } + + /** + * @hide + */ + public void onRangingReconfigureFailed(@Callback.Reason int reason, + @NonNull PersistableBundle params) { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingReconfigureFailed invoked for a closed session"); + return; + } + + executeCallback(() -> mCallback.onReconfigureFailed(reason, params)); + } + + /** + * @hide + */ + public void onRangingStopped() { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingStopped invoked for a closed session"); + return; } + + mState = State.IDLE; + executeCallback(() -> mCallback.onStopped()); + } + + /** + * @hide + */ + public void onRangingStopFailed(@Callback.Reason int reason, + @NonNull PersistableBundle params) { + if (mState == State.CLOSED) { + Log.w(TAG, "onRangingStopFailed invoked for a closed session"); + return; + } + + executeCallback(() -> mCallback.onStopFailed(reason, params)); + } + + /** + * @hide + */ + public void onRangingClosed(@Callback.Reason int reason, + @NonNull PersistableBundle parameters) { + mState = State.CLOSED; + executeCallback(() -> mCallback.onClosed(reason, parameters)); } /** @@ -243,9 +467,16 @@ public final class RangingSession implements AutoCloseable { return; } + executeCallback(() -> mCallback.onReportReceived(report)); + } + + /** + * @hide + */ + private void executeCallback(@NonNull Runnable runnable) { final long identity = Binder.clearCallingIdentity(); try { - mExecutor.execute(() -> mCallback.onReportReceived(report)); + mExecutor.execute(runnable); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/core/java/android/uwb/StartFailureReason.aidl b/core/java/android/uwb/StartFailureReason.aidl deleted file mode 100644 index 4d9c962f529b..000000000000 --- a/core/java/android/uwb/StartFailureReason.aidl +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.uwb; - -/** - * @hide - */ -@Backing(type="int") -enum StartFailureReason { - /** - * Unknown start failure reason - */ - UNKNOWN, - - /** - * The provided parameters were invalid and ranging could not start - */ - BAD_PARAMETERS, - - /** - * The maximum number of sessions has been reached. This error may be generated - * for an active session if a higher priority session begins. - */ - MAX_SESSIONS_REACHED, - - /** - * The system state has changed resulting in the session ending (e.g. the user - * disables UWB, or the user's locale changes and an active channel is no longer - * permitted to be used). - */ - SYSTEM_POLICY, - - /** - * The session could not start because of a protocol specific reason. - */ - PROTOCOL_SPECIFIC, -} - diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java index f4d801868e18..15ee5b5f22eb 100644 --- a/core/java/android/uwb/UwbManager.java +++ b/core/java/android/uwb/UwbManager.java @@ -369,13 +369,13 @@ public final class UwbManager { /** * Open a {@link RangingSession} with the given parameters - * <p>This function is asynchronous and will return before ranging begins. The - * {@link RangingSession.Callback#onOpenSuccess(RangingSession, PersistableBundle)} function is - * called with a {@link RangingSession} object used to control ranging when the session is - * successfully opened. + * <p>The {@link RangingSession.Callback#onOpened(RangingSession)} function is called with a + * {@link RangingSession} object used to control ranging when the session is successfully + * opened. * - * <p>If a session cannot be opened, then {@link RangingSession.Callback#onClosed(int)} will be - * invoked with the appropriate {@link RangingSession.Callback.CloseReason}. + * <p>If a session cannot be opened, then + * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} will be invoked with the + * appropriate {@link RangingSession.Callback.Reason}. * * <p>An open {@link RangingSession} will be automatically closed if client application process * dies. @@ -391,7 +391,7 @@ public final class UwbManager { * @return an {@link AutoCloseable} that is able to be used to close or cancel the opening of a * {@link RangingSession} that has been requested through {@link #openRangingSession} * but has not yet been made available by - * {@link RangingSession.Callback#onOpenSuccess}. + * {@link RangingSession.Callback#onOpened(RangingSession)}. */ @NonNull public AutoCloseable openRangingSession(@NonNull PersistableBundle parameters, diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index c87e51e35891..37f0a64df613 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -519,8 +519,13 @@ public class KeyEvent extends InputEvent implements Parcelable { /** Key code constant: Settings key. * Starts the system settings activity. */ public static final int KEYCODE_SETTINGS = 176; - /** Key code constant: TV power key. - * On TV remotes, toggles the power on a television screen. */ + /** + * Key code constant: TV power key. + * On HDMI TV panel devices and Android TV devices that don't support HDMI, toggles the power + * state of the device. + * On HDMI source devices, toggles the power state of the HDMI-connected TV via HDMI-CEC and + * makes the source device follow this power state. + */ public static final int KEYCODE_TV_POWER = 177; /** Key code constant: TV input key. * On TV remotes, switches the input on a television screen. */ diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index 673ed0d8b95d..5ce4c50cc85c 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -156,14 +156,25 @@ public class NotificationHeaderView extends FrameLayout { * Sets the extra margin at the end of the top line of left-aligned text + icons. * This value will have the margin required to accommodate the expand button added to it. * - * @param extraMarginEnd extra margin + * @param extraMarginEnd extra margin in px */ - @RemotableViewMethod public void setTopLineExtraMarginEnd(int extraMarginEnd) { mTopLineView.setHeaderTextMarginEnd(extraMarginEnd + mHeadingEndMargin); } /** + * Sets the extra margin at the end of the top line of left-aligned text + icons. + * This value will have the margin required to accommodate the expand button added to it. + * + * @param extraMarginEndDp extra margin in dp + */ + @RemotableViewMethod + public void setTopLineExtraMarginEndDp(float extraMarginEndDp) { + setTopLineExtraMarginEnd( + (int) (extraMarginEndDp * getResources().getDisplayMetrics().density)); + } + + /** * Get the current margin end value for the header text. * Add this to {@link #getTopLineBaseMarginEnd()} to get the total margin of the top line. * diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index f642d7580d4d..2f973571fff7 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -333,6 +333,8 @@ public final class SurfaceControl implements Parcelable { */ public long mNativeObject; private long mNativeHandle; + private boolean mDebugRelease = false; + private Throwable mReleaseStack = null; // TODO: Move width/height to native and fix locking through out. private final Object mLock = new Object(); @@ -580,6 +582,13 @@ public final class SurfaceControl implements Parcelable { } mNativeObject = nativeObject; mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0; + if (mNativeObject == 0) { + if (mDebugRelease) { + mReleaseStack = new Throwable("assigned zero nativeObject here"); + } + } else { + mReleaseStack = null; + } } /** @@ -590,6 +599,7 @@ public final class SurfaceControl implements Parcelable { mWidth = other.mWidth; mHeight = other.mHeight; mLocalOwnerView = other.mLocalOwnerView; + mDebugRelease = other.mDebugRelease; assignNativeObject(nativeCopyFromSurfaceControl(other.mNativeObject), callsite); } @@ -1419,6 +1429,7 @@ public final class SurfaceControl implements Parcelable { mName = in.readString8(); mWidth = in.readInt(); mHeight = in.readInt(); + mDebugRelease = in.readBoolean(); long object = 0; if (in.readInt() != 0) { @@ -1437,8 +1448,12 @@ public final class SurfaceControl implements Parcelable { dest.writeString8(mName); dest.writeInt(mWidth); dest.writeInt(mHeight); + dest.writeBoolean(mDebugRelease); if (mNativeObject == 0) { dest.writeInt(0); + if (mReleaseStack != null) { + Log.w(TAG, "Sending invalid " + this + " caused by:", mReleaseStack); + } } else { dest.writeInt(1); } @@ -1450,6 +1465,13 @@ public final class SurfaceControl implements Parcelable { } /** + * @hide + */ + public void setDebugRelease(boolean debug) { + mDebugRelease = debug; + } + + /** * Checks whether two {@link SurfaceControl} objects represent the same surface. * * @param other The other object to check @@ -1519,6 +1541,9 @@ public final class SurfaceControl implements Parcelable { nativeRelease(mNativeObject); mNativeObject = 0; mNativeHandle = 0; + if (mDebugRelease) { + mReleaseStack = new Throwable("released here"); + } mCloseGuard.close(); } } @@ -1534,8 +1559,11 @@ public final class SurfaceControl implements Parcelable { } private void checkNotReleased() { - if (mNativeObject == 0) throw new NullPointerException( - "Invalid " + this + ", mNativeObject is null. Have you called release() already?"); + if (mNativeObject == 0) { + Log.wtf(TAG, "Invalid " + this + " caused by:", mReleaseStack); + throw new NullPointerException( + "mNativeObject of " + this + " is null. Have you called release() already?"); + } } /** @@ -2383,6 +2411,7 @@ public final class SurfaceControl implements Parcelable { public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf) { long nativeObj = nativeMirrorSurface(mirrorOf.mNativeObject); SurfaceControl sc = new SurfaceControl(); + sc.mDebugRelease = mirrorOf.mDebugRelease; sc.assignNativeObject(nativeObj, "mirrorSurface"); return sc; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0f5321548f95..e8584dac90c8 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8821,6 +8821,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, structure.setAutofillValue(getAutofillValue()); } structure.setImportantForAutofill(getImportantForAutofill()); + structure.setOnReceiveContentMimeTypes(getOnReceiveContentMimeTypes()); } int ignoredParentLeft = 0; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index d863ea5d2c01..0097b2e107a1 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3150,6 +3150,8 @@ public final class ViewRootImpl implements ViewParent, mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes); + final boolean wasReportNextDraw = mReportNextDraw; + // Remember if we must report the next draw. if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { reportNextDraw(); @@ -3177,12 +3179,25 @@ public final class ViewRootImpl implements ViewParent, if (isViewVisible) { // Try again scheduleTraversals(); - } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) { - for (int i = 0; i < mPendingTransitions.size(); ++i) { - mPendingTransitions.get(i).endChangingAnimations(); + } else { + if (mPendingTransitions != null && mPendingTransitions.size() > 0) { + for (int i = 0; i < mPendingTransitions.size(); ++i) { + mPendingTransitions.get(i).endChangingAnimations(); + } + mPendingTransitions.clear(); + } + + // We may never draw since it's not visible. Report back that we're finished + // drawing. + if (!wasReportNextDraw && mReportNextDraw) { + mReportNextDraw = false; + pendingDrawFinished(); } - mPendingTransitions.clear(); } + + // We were unable to draw this traversal. Unset this flag since we'll block without + // ever being able to draw again + mNextDrawUseBlastSync = false; } if (mAttachInfo.mContentCaptureEvents != null) { diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 29ce231d5d87..f5aa97a88608 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -372,6 +372,14 @@ public abstract class ViewStructure { public void setImportantForAutofill(@AutofillImportance int mode) {} /** + * Sets the MIME types accepted by this view. See {@link View#getOnReceiveContentMimeTypes()}. + * + * <p>Should only be set when the node is used for Autofill or Content Capture purposes - it + * will be ignored when used for Assist. + */ + public void setOnReceiveContentMimeTypes(@Nullable String[] mimeTypes) {} + + /** * Sets the {@link android.text.InputType} bits of this node. * * @param inputType inputType bits as defined by {@link android.text.InputType}. diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 6d88d637dc24..482f2f07499d 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -386,6 +386,16 @@ public interface WindowManager extends ViewManager { * @hide */ int TRANSIT_KEYGUARD_UNOCCLUDE = 9; + /** + * The first slot for custom transition types. Callers (like Shell) can make use of custom + * transition types for dealing with special cases. These types are effectively ignored by + * Core and will just be passed along as part of TransitionInfo objects. An example is + * split-screen using a custom type for it's snap-to-dismiss action. By using a custom type, + * Shell can properly dispatch the results of that transition to the split-screen + * implementation. + * @hide + */ + int TRANSIT_FIRST_CUSTOM = 10; /** * @hide @@ -401,6 +411,7 @@ public interface WindowManager extends ViewManager { TRANSIT_KEYGUARD_GOING_AWAY, TRANSIT_KEYGUARD_OCCLUDE, TRANSIT_KEYGUARD_UNOCCLUDE, + TRANSIT_FIRST_CUSTOM }) @Retention(RetentionPolicy.SOURCE) @interface TransitionType {} diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java index 82d52b67c06a..ae145de21190 100644 --- a/core/java/android/view/autofill/AutofillId.java +++ b/core/java/android/view/autofill/AutofillId.java @@ -143,6 +143,7 @@ public final class AutofillId implements Parcelable { * * @hide */ + @TestApi public boolean isNonVirtual() { return !isVirtualInt() && !isVirtualLong(); } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 364ae8186e54..794181e388cf 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -32,6 +32,9 @@ import android.annotation.RequiresFeature; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.app.assist.AssistStructure.ViewNode; +import android.app.assist.AssistStructure.ViewNodeBuilder; +import android.app.assist.AssistStructure.ViewNodeParcelable; import android.content.AutofillOptions; import android.content.ClipData; import android.content.ComponentName; @@ -64,6 +67,8 @@ import android.view.Choreographer; import android.view.ContentInfo; import android.view.KeyEvent; import android.view.View; +import android.view.ViewRootImpl; +import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -3581,6 +3586,35 @@ public final class AutofillManager { mAfm = new WeakReference<>(autofillManager); } + @Nullable + @Override + public ViewNodeParcelable getViewNodeParcelable(@NonNull AutofillId id) { + final AutofillManager afm = mAfm.get(); + if (afm == null) return null; + + final View view = getView(afm, id); + if (view == null) { + Log.w(TAG, "getViewNodeParcelable(" + id + "): could not find view"); + return null; + } + final ViewRootImpl root = view.getViewRootImpl(); + if (root != null + && (root.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) == 0) { + ViewNodeBuilder viewStructure = new ViewNodeBuilder(); + viewStructure.setAutofillId(view.getAutofillId()); + view.onProvideAutofillStructure(viewStructure, /* flags= */ 0); + // TODO(b/141703532): We don't call View#onProvideAutofillVirtualStructure for + // efficiency reason. But this also means we will return null for virtual views + // for now. We will add a new API to fetch the view node info of the virtual + // child view. + ViewNode viewNode = viewStructure.getViewNode(); + if (viewNode != null && id.equals(viewNode.getAutofillId())) { + return new ViewNodeParcelable(viewNode); + } + } + return null; + } + @Override public Rect getViewCoordinates(@NonNull AutofillId id) { final AutofillManager afm = mAfm.get(); diff --git a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl index 8526c1e443c8..7d08bcf34398 100644 --- a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl +++ b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl @@ -18,6 +18,7 @@ package android.view.autofill; import java.util.List; +import android.app.assist.AssistStructure; import android.graphics.Rect; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; @@ -36,6 +37,11 @@ interface IAugmentedAutofillManagerClient { Rect getViewCoordinates(in AutofillId id); /** + * Gets the autofill view structure of the input field view. + */ + AssistStructure.ViewNodeParcelable getViewNodeParcelable(in AutofillId id); + + /** * Autofills the activity with the contents of the values. */ void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values, diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java index e731d4b76fb9..4e9c229e03a0 100644 --- a/core/java/android/view/contentcapture/ViewNode.java +++ b/core/java/android/view/contentcapture/ViewNode.java @@ -81,6 +81,7 @@ public final class ViewNode extends AssistStructure.ViewNode { private static final long FLAGS_HAS_AUTOFILL_HINTS = 1L << 33; private static final long FLAGS_HAS_AUTOFILL_OPTIONS = 1L << 34; private static final long FLAGS_HAS_HINT_ID_ENTRY = 1L << 35; + private static final long FLAGS_HAS_MIME_TYPES = 1L << 36; /** Flags used to optimize what's written to the parcel */ private long mFlags; @@ -113,6 +114,7 @@ public final class ViewNode extends AssistStructure.ViewNode { private String[] mAutofillHints; private AutofillValue mAutofillValue; private CharSequence[] mAutofillOptions; + private String[] mOnReceiveContentMimeTypes; /** @hide */ public ViewNode() { @@ -169,6 +171,9 @@ public final class ViewNode extends AssistStructure.ViewNode { if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) { mLocaleList = parcel.readParcelable(null); } + if ((nodeFlags & FLAGS_HAS_MIME_TYPES) != 0) { + mOnReceiveContentMimeTypes = parcel.readStringArray(); + } if ((nodeFlags & FLAGS_HAS_INPUT_TYPE) != 0) { mInputType = parcel.readInt(); } @@ -463,6 +468,12 @@ public final class ViewNode extends AssistStructure.ViewNode { return mAutofillOptions; } + @Override + @Nullable + public String[] getOnReceiveContentMimeTypes() { + return mOnReceiveContentMimeTypes; + } + @Nullable @Override public LocaleList getLocaleList() { @@ -508,6 +519,9 @@ public final class ViewNode extends AssistStructure.ViewNode { if (mLocaleList != null) { nodeFlags |= FLAGS_HAS_LOCALE_LIST; } + if (mOnReceiveContentMimeTypes != null) { + nodeFlags |= FLAGS_HAS_MIME_TYPES; + } if (mInputType != 0) { nodeFlags |= FLAGS_HAS_INPUT_TYPE; } @@ -584,6 +598,9 @@ public final class ViewNode extends AssistStructure.ViewNode { if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) { parcel.writeParcelable(mLocaleList, 0); } + if ((nodeFlags & FLAGS_HAS_MIME_TYPES) != 0) { + parcel.writeStringArray(mOnReceiveContentMimeTypes); + } if ((nodeFlags & FLAGS_HAS_INPUT_TYPE) != 0) { parcel.writeInt(mInputType); } @@ -912,6 +929,11 @@ public final class ViewNode extends AssistStructure.ViewNode { } @Override + public void setOnReceiveContentMimeTypes(@Nullable String[] mimeTypes) { + mNode.mOnReceiveContentMimeTypes = mimeTypes; + } + + @Override public void setAutofillHints(String[] hints) { mNode.mAutofillHints = hints; } diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index f057c1239e52..415b3a766d16 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -163,11 +163,13 @@ public class BaseInputConnection implements InputConnection { } /** - * Default implementation calls {@link #finishComposingText()}. + * Default implementation calls {@link #finishComposingText()} and + * {@code setImeTemporarilyConsumesInput(false)}. */ @CallSuper public void closeConnection() { finishComposingText(); + setImeTemporarilyConsumesInput(false); } /** diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index 1cf25a77fa38..2df75f6570db 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -25,6 +25,7 @@ import static android.view.inputmethod.EditorInfoProto.PRIVATE_IME_OPTIONS; import static android.view.inputmethod.EditorInfoProto.TARGET_INPUT_METHOD_USER_ID; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -36,7 +37,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; import android.text.InputType; -import android.text.ParcelableSpan; import android.text.TextUtils; import android.util.Printer; import android.util.proto.ProtoOutputStream; @@ -44,6 +44,7 @@ import android.view.View; import android.view.autofill.AutofillId; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -563,8 +564,9 @@ public class EditorInfo implements InputType, Parcelable { @VisibleForTesting static final int MAX_INITIAL_SELECTION_LENGTH = MEMORY_EFFICIENT_TEXT_LENGTH / 2; - @NonNull - private InitialSurroundingText mInitialSurroundingText = new InitialSurroundingText(); + @Nullable + private SurroundingText mInitialSurroundingText = null; + /** * Editors may use this method to provide initial input text to IMEs. As the surrounding text @@ -607,6 +609,12 @@ public class EditorInfo implements InputType, Parcelable { public void setInitialSurroundingSubText(@NonNull CharSequence subText, int subTextStart) { Objects.requireNonNull(subText); + // For privacy protection reason, we don't carry password inputs to IMEs. + if (isPasswordInputType(inputType)) { + mInitialSurroundingText = null; + return; + } + // Swap selection start and end if necessary. final int subTextSelStart = initialSelStart > initialSelEnd ? initialSelEnd - subTextStart : initialSelStart - subTextStart; @@ -616,23 +624,17 @@ public class EditorInfo implements InputType, Parcelable { final int subTextLength = subText.length(); // Unknown or invalid selection. if (subTextStart < 0 || subTextSelStart < 0 || subTextSelEnd > subTextLength) { - mInitialSurroundingText = new InitialSurroundingText(); - return; - } - - // For privacy protection reason, we don't carry password inputs to IMEs. - if (isPasswordInputType(inputType)) { - mInitialSurroundingText = new InitialSurroundingText(); + mInitialSurroundingText = null; return; } if (subTextLength <= MEMORY_EFFICIENT_TEXT_LENGTH) { - mInitialSurroundingText = new InitialSurroundingText(subText, subTextSelStart, - subTextSelEnd); + mInitialSurroundingText = new SurroundingText(subText, subTextSelStart, + subTextSelEnd, subTextStart); return; } - trimLongSurroundingText(subText, subTextSelStart, subTextSelEnd); + trimLongSurroundingText(subText, subTextSelStart, subTextSelEnd, subTextStart); } /** @@ -651,8 +653,10 @@ public class EditorInfo implements InputType, Parcelable { * @param subText The long text that needs to be trimmed. * @param selStart The text offset of the start of the selection. * @param selEnd The text offset of the end of the selection + * @param subTextStart The position that the input text got trimmed. */ - private void trimLongSurroundingText(CharSequence subText, int selStart, int selEnd) { + private void trimLongSurroundingText(CharSequence subText, int selStart, int selEnd, + int subTextStart) { final int sourceSelLength = selEnd - selStart; // When the selected text is too long, drop it. final int newSelLength = (sourceSelLength > MAX_INITIAL_SELECTION_LENGTH) @@ -702,10 +706,13 @@ public class EditorInfo implements InputType, Parcelable { // obj. newBeforeCursorHead = 0; final int newSelHead = newBeforeCursorHead + newBeforeCursorLength; - mInitialSurroundingText = new InitialSurroundingText( - newInitialSurroundingText, newSelHead, newSelHead + newSelLength); + final int newOffset = subTextStart + selStart - newSelHead; + mInitialSurroundingText = new SurroundingText( + newInitialSurroundingText, newSelHead, newSelHead + newSelLength, + newOffset); } + /** * Get <var>length</var> characters of text before the current cursor position. May be * {@code null} when the protocol is not supported. @@ -720,7 +727,17 @@ public class EditorInfo implements InputType, Parcelable { */ @Nullable public CharSequence getInitialTextBeforeCursor(int length, int flags) { - return mInitialSurroundingText.getInitialTextBeforeCursor(length, flags); + if (mInitialSurroundingText == null) { + return null; + } + + int selStart = Math.min(mInitialSurroundingText.getSelectionStart(), + mInitialSurroundingText.getSelectionEnd()); + int n = Math.min(length, selStart); + return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) + ? mInitialSurroundingText.getText().subSequence(selStart - n, selStart) + : TextUtils.substring(mInitialSurroundingText.getText(), selStart - n, + selStart); } /** @@ -735,6 +752,10 @@ public class EditorInfo implements InputType, Parcelable { */ @Nullable public CharSequence getInitialSelectedText(int flags) { + if (mInitialSurroundingText == null) { + return null; + } + // Swap selection start and end if necessary. final int correctedTextSelStart = initialSelStart > initialSelEnd ? initialSelEnd : initialSelStart; @@ -742,11 +763,21 @@ public class EditorInfo implements InputType, Parcelable { ? initialSelStart : initialSelEnd; final int sourceSelLength = correctedTextSelEnd - correctedTextSelStart; - if (initialSelStart < 0 || initialSelEnd < 0 - || mInitialSurroundingText.getSelectionLength() != sourceSelLength) { + int selStart = mInitialSurroundingText.getSelectionStart(); + int selEnd = mInitialSurroundingText.getSelectionEnd(); + if (selStart > selEnd) { + int tmp = selStart; + selStart = selEnd; + selEnd = tmp; + } + final int selLength = selEnd - selStart; + if (initialSelStart < 0 || initialSelEnd < 0 || selLength != sourceSelLength) { return null; } - return mInitialSurroundingText.getInitialSelectedText(flags); + + return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) + ? mInitialSurroundingText.getText().subSequence(selStart, selEnd) + : TextUtils.substring(mInitialSurroundingText.getText(), selStart, selEnd); } /** @@ -763,7 +794,79 @@ public class EditorInfo implements InputType, Parcelable { */ @Nullable public CharSequence getInitialTextAfterCursor(int length, int flags) { - return mInitialSurroundingText.getInitialTextAfterCursor(length, flags); + if (mInitialSurroundingText == null) { + return null; + } + + int surroundingTextLength = mInitialSurroundingText.getText().length(); + int selEnd = Math.max(mInitialSurroundingText.getSelectionStart(), + mInitialSurroundingText.getSelectionEnd()); + int n = Math.min(length, surroundingTextLength - selEnd); + return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) + ? mInitialSurroundingText.getText().subSequence(selEnd, selEnd + n) + : TextUtils.substring(mInitialSurroundingText.getText(), selEnd, selEnd + n); + } + + /** + * Gets the surrounding text around the current cursor, with <var>beforeLength</var> characters + * of text before the cursor (start of the selection), <var>afterLength</var> characters of text + * after the cursor (end of the selection), and all of the selected text. + * + * <p>The initial surrounding text for return could be trimmed if oversize. Fundamental trimming + * rules are:</p> + * <ul> + * <li>The text before the cursor is the most important information to IMEs.</li> + * <li>The text after the cursor is the second important information to IMEs.</li> + * <li>The selected text is the least important information but it shall NEVER be truncated. + * When it is too long, just drop it.</li> + * </ul> + * + * <p>For example, the subText can be viewed as TextBeforeCursor + Selection + TextAfterCursor. + * The result could be:</p> + * <ol> + * <li>(maybeTrimmedAtHead)TextBeforeCursor + Selection + * + TextAfterCursor(maybeTrimmedAtTail)</li> + * <li>(maybeTrimmedAtHead)TextBeforeCursor + TextAfterCursor(maybeTrimmedAtTail)</li> + * </ol> + * + * @param beforeLength The expected length of the text before the cursor. + * @param afterLength The expected length of the text after the cursor. + * @param flags Supplies additional options controlling how the text is returned. May be either + * {@code 0} or {@link InputConnection#GET_TEXT_WITH_STYLES}. + * @return an {@link android.view.inputmethod.SurroundingText} object describing the surrounding + * text and state of selection, or {@code null} if the editor or system could not support this + * protocol. + * @throws IllegalArgumentException if {@code beforeLength} or {@code afterLength} is negative. + */ + @Nullable + public SurroundingText getInitialSurroundingText( + @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, + @InputConnection.GetTextType int flags) { + Preconditions.checkArgumentNonnegative(beforeLength); + Preconditions.checkArgumentNonnegative(afterLength); + + if (mInitialSurroundingText == null) { + return null; + } + + int length = mInitialSurroundingText.getText().length(); + int selStart = mInitialSurroundingText.getSelectionStart(); + int selEnd = mInitialSurroundingText.getSelectionEnd(); + if (selStart > selEnd) { + int tmp = selStart; + selStart = selEnd; + selEnd = tmp; + } + + int before = Math.min(beforeLength, selStart); + int after = Math.min(selEnd + afterLength, length); + int offset = selStart - before; + CharSequence newText = ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) + ? mInitialSurroundingText.getText().subSequence(offset, after) + : TextUtils.substring(mInitialSurroundingText.getText(), offset, after); + int newSelEnd = Math.min(selEnd - offset, length); + return new SurroundingText(newText, before, newSelEnd, + mInitialSurroundingText.getOffset() + offset); } private static boolean isCutOnSurrogate(CharSequence sourceText, int cutPosition, @@ -893,7 +996,10 @@ public class EditorInfo implements InputType, Parcelable { dest.writeInt(fieldId); dest.writeString(fieldName); dest.writeBundle(extras); - mInitialSurroundingText.writeToParcel(dest, flags); + dest.writeBoolean(mInitialSurroundingText != null); + if (mInitialSurroundingText != null) { + mInitialSurroundingText.writeToParcel(dest, flags); + } if (hintLocales != null) { hintLocales.writeToParcel(dest, flags); } else { @@ -925,9 +1031,11 @@ public class EditorInfo implements InputType, Parcelable { res.fieldId = source.readInt(); res.fieldName = source.readString(); res.extras = source.readBundle(); - InitialSurroundingText initialSurroundingText = - InitialSurroundingText.CREATOR.createFromParcel(source); - res.mInitialSurroundingText = initialSurroundingText; + boolean hasInitialSurroundingText = source.readBoolean(); + if (hasInitialSurroundingText) { + res.mInitialSurroundingText = + SurroundingText.CREATOR.createFromParcel(source); + } LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source); res.hintLocales = hintLocales.isEmpty() ? null : hintLocales; res.contentMimeTypes = source.readStringArray(); @@ -943,120 +1051,4 @@ public class EditorInfo implements InputType, Parcelable { public int describeContents() { return 0; } - - static final class InitialSurroundingText implements Parcelable { - @Nullable final CharSequence mSurroundingText; - final int mSelectionHead; - final int mSelectionEnd; - - InitialSurroundingText() { - mSurroundingText = null; - mSelectionHead = 0; - mSelectionEnd = 0; - } - - InitialSurroundingText(@Nullable CharSequence surroundingText, int selectionHead, - int selectionEnd) { - // Copy the original text (without NoCopySpan) in case the original text is updated - // later. - mSurroundingText = copyWithParcelableSpans(surroundingText); - mSelectionHead = selectionHead; - mSelectionEnd = selectionEnd; - } - - @Nullable - private CharSequence getInitialTextBeforeCursor(int n, int flags) { - if (mSurroundingText == null) { - return null; - } - - final int length = Math.min(n, mSelectionHead); - return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) - ? mSurroundingText.subSequence(mSelectionHead - length, mSelectionHead) - : TextUtils.substring(mSurroundingText, mSelectionHead - length, - mSelectionHead); - } - - @Nullable - private CharSequence getInitialSelectedText(int flags) { - if (mSurroundingText == null) { - return null; - } - - return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) - ? mSurroundingText.subSequence(mSelectionHead, mSelectionEnd) - : TextUtils.substring(mSurroundingText, mSelectionHead, mSelectionEnd); - } - - @Nullable - private CharSequence getInitialTextAfterCursor(int n, int flags) { - if (mSurroundingText == null) { - return null; - } - - final int length = Math.min(n, mSurroundingText.length() - mSelectionEnd); - return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) - ? mSurroundingText.subSequence(mSelectionEnd, mSelectionEnd + length) - : TextUtils.substring(mSurroundingText, mSelectionEnd, mSelectionEnd + length); - } - - private int getSelectionLength() { - return mSelectionEnd - mSelectionHead; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - TextUtils.writeToParcel(mSurroundingText, dest, flags); - dest.writeInt(mSelectionHead); - dest.writeInt(mSelectionEnd); - } - - public static final @android.annotation.NonNull Parcelable.Creator<InitialSurroundingText> - CREATOR = new Parcelable.Creator<InitialSurroundingText>() { - @Override - public InitialSurroundingText createFromParcel(Parcel source) { - final CharSequence initialText = - TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - final int selectionHead = source.readInt(); - final int selectionEnd = source.readInt(); - - return new InitialSurroundingText(initialText, selectionHead, selectionEnd); - } - - @Override - public InitialSurroundingText[] newArray(int size) { - return new InitialSurroundingText[size]; - } - }; - - /** - * Create a copy of the given {@link CharSequence} object, with completely copy - * {@link ParcelableSpan} instances. - * - * @param source the original {@link CharSequence} to be copied. - * @return the copied {@link CharSequence}. {@code null} if {@code source} is {@code null}. - */ - @Nullable - private static CharSequence copyWithParcelableSpans(@Nullable CharSequence source) { - if (source == null) { - return null; - } - Parcel parcel = null; - try { - parcel = Parcel.obtain(); - TextUtils.writeToParcel(source, parcel, /* parcelableFlags= */ 0); - parcel.setDataPosition(0); - return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); - } finally { - if (parcel != null) { - parcel.recycle(); - } - } - } - } -} +}
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 8c81143e4cdc..a76d46d1c00f 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -1002,4 +1002,22 @@ public interface InputConnection { */ boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags, @Nullable Bundle opts); + + /** + * Called by the input method to indicate that it temporarily consumes all input for itself, + * or no longer does so. + * + * <p>Editors should reflect that they are temporarily not receiving input by hiding the + * cursor if {@code imeTemporarilyConsumesInput} is {@code true}, and resume showing the + * cursor if it is {@code false}. + * + * @param imeTemporarilyConsumesInput {@code true} when the IME is temporarily consuming input + * and the cursor should be hidden, {@code false} when input to the editor resumes and the + * cursor should be shown again. + * @return {@code true} on success, {@code false} if the input connection is no longer valid, or + * the protocol is not supported. + */ + default boolean setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) { + return false; + } } diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index ca853485d4f3..b29149fc1fa0 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -335,4 +335,13 @@ public class InputConnectionWrapper implements InputConnection { public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { return mTarget.commitContent(inputContentInfo, flags, opts); } + + /** + * {@inheritDoc} + * @throws NullPointerException if the target is {@code null}. + */ + @Override + public boolean setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) { + return mTarget.setImeTemporarilyConsumesInput(imeTemporarilyConsumesInput); + } } diff --git a/core/java/android/view/inputmethod/SurroundingText.java b/core/java/android/view/inputmethod/SurroundingText.java index 94e05a830b85..c85a18a1df79 100644 --- a/core/java/android/view/inputmethod/SurroundingText.java +++ b/core/java/android/view/inputmethod/SurroundingText.java @@ -18,6 +18,7 @@ package android.view.inputmethod; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -74,7 +75,7 @@ public final class SurroundingText implements Parcelable { public SurroundingText(@NonNull final CharSequence text, @IntRange(from = 0) int selectionStart, @IntRange(from = 0) int selectionEnd, @IntRange(from = -1) int offset) { - mText = text; + mText = copyWithParcelableSpans(text); mSelectionStart = selectionStart; mSelectionEnd = selectionEnd; mOffset = offset; @@ -155,4 +156,29 @@ public final class SurroundingText implements Parcelable { return new SurroundingText[size]; } }; + + /** + * Create a copy of the given {@link CharSequence} object, with completely copy + * {@link ParcelableSpan} instances. + * + * @param source the original {@link CharSequence} to be copied. + * @return the copied {@link CharSequence}. {@code null} if {@code source} is {@code null}. + */ + @Nullable + private static CharSequence copyWithParcelableSpans(@Nullable CharSequence source) { + if (source == null) { + return null; + } + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + TextUtils.writeToParcel(source, parcel, /* parcelableFlags= */ 0); + parcel.setDataPosition(0); + return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + } finally { + if (parcel != null) { + parcel.recycle(); + } + } + } } diff --git a/core/java/android/view/translation/ITranslationDirectManager.aidl b/core/java/android/view/translation/ITranslationDirectManager.aidl new file mode 100644 index 000000000000..358f99a5104b --- /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 000000000000..73addf4d1894 --- /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 000000000000..a1e663aa8ff7 --- /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 000000000000..40f21a6b3d4e --- /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 000000000000..6554e1a1db54 --- /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 000000000000..c34bf3011462 --- /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 000000000000..a5e3f758ba9f --- /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 000000000000..e5350bb54dc2 --- /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 000000000000..d29063fbd914 --- /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 000000000000..875d798370d4 --- /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 000000000000..ab1bc477e0fd --- /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 000000000000..675f32b19d17 --- /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/webkit/TEST_MAPPING b/core/java/android/webkit/TEST_MAPPING new file mode 100644 index 000000000000..bd25200ffc38 --- /dev/null +++ b/core/java/android/webkit/TEST_MAPPING @@ -0,0 +1,36 @@ +{ + "presubmit": [ + { + "name": "CtsWebkitTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "CtsHostsideWebViewTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "GtsWebViewTestCases", + "options": [ + { + "exclude-annotation": "android.test.FlakyTest" + } + ] + }, + { + "name": "GtsWebViewHostTestCases", + "options": [ + { + "exclude-annotation": "android.test.FlakyTest" + } + ] + } + ] +} diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 0025d1e2a853..26dd5e309fa7 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -99,6 +99,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.OnReceiveContentListener; import android.view.SubMenu; import android.view.View; import android.view.View.DragShadowBuilder; @@ -588,8 +589,18 @@ public class Editor { mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this); } - @VisibleForTesting - public @NonNull TextViewOnReceiveContentListener getDefaultOnReceiveContentListener() { + /** + * Returns the default handler for receiving content in an editable {@link TextView}. This + * listener impl is used to encapsulate the default behavior but it is not part of the public + * API. If an app wants to execute the default platform behavior for receiving content, it + * should call {@link View#onReceiveContent}. Alternatively, if an app implements a custom + * listener for receiving content and wants to delegate some of the content to be handled by + * the platform, it should return the corresponding content from its listener. See + * {@link View#setOnReceiveContentListener} and {@link OnReceiveContentListener} for more info. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + @NonNull + public TextViewOnReceiveContentListener getDefaultOnReceiveContentListener() { return mDefaultOnReceiveContentListener; } @@ -6613,6 +6624,9 @@ public class Editor { } updateSelection(event); + if (mTextView.hasSelection() && mEndHandle != null) { + mEndHandle.updateMagnifier(event); + } break; case MotionEvent.ACTION_UP: @@ -6623,6 +6637,9 @@ public class Editor { break; } updateSelection(event); + if (mEndHandle != null) { + mEndHandle.dismissMagnifier(); + } // No longer dragging to select text, let the parent intercept events. mTextView.getParent().requestDisallowInterceptTouchEvent(false); diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index cf0e0d1f1842..5e74381dce22 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -73,7 +73,7 @@ import java.util.function.Predicate; /** * <p>Displays a vertically-scrollable collection of views, where each view is positioned * immediatelybelow the previous view in the list. For a more modern, flexible, and performant - * approach to displaying lists, use {@link android.support.v7.widget.RecyclerView}.</p> + * approach to displaying lists, use {@link androidx.recyclerview.widget.RecyclerView}.</p> * * <p>To display a list, you can include a list view in your layout XML file:</p> * diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 4ba1ca8989b7..8dafc5db6178 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -18,10 +18,13 @@ package android.widget; import android.annotation.ColorInt; import android.annotation.DimenRes; +import android.annotation.DrawableRes; +import android.annotation.IdRes; import android.annotation.IntDef; import android.annotation.LayoutRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.Px; import android.annotation.StyleRes; import android.app.Activity; import android.app.ActivityOptions; @@ -63,12 +66,17 @@ import android.util.ArrayMap; import android.util.IntArray; import android.util.Log; import android.util.Pair; +import android.util.TypedValue; +import android.util.TypedValue.ComplexDimensionUnit; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.LayoutInflater.Filter; import android.view.RemotableViewMethod; import android.view.View; import android.view.ViewGroup; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.ViewManager; +import android.view.ViewParent; import android.view.ViewStub; import android.widget.AdapterView.OnItemClickListener; @@ -171,6 +179,49 @@ public class RemoteViews implements Parcelable, Filter { private static final int OVERRIDE_TEXT_COLORS_TAG = 20; private static final int SET_RIPPLE_DRAWABLE_COLOR_TAG = 21; private static final int SET_INT_TAG_TAG = 22; + private static final int REMOVE_FROM_PARENT_ACTION_TAG = 23; + + /** @hide **/ + @IntDef(prefix = "MARGIN_", value = { + MARGIN_LEFT, + MARGIN_TOP, + MARGIN_RIGHT, + MARGIN_BOTTOM, + MARGIN_START, + MARGIN_END + }) + @Retention(RetentionPolicy.SOURCE) + public @interface MarginType {} + /** + * The value will apply to the marginLeft. + * @hide + */ + public static final int MARGIN_LEFT = 0; + /** + * The value will apply to the marginTop. + * @hide + */ + public static final int MARGIN_TOP = 1; + /** + * The value will apply to the marginRight. + * @hide + */ + public static final int MARGIN_RIGHT = 2; + /** + * The value will apply to the marginBottom. + * @hide + */ + public static final int MARGIN_BOTTOM = 3; + /** + * The value will apply to the marginStart. + * @hide + */ + public static final int MARGIN_START = 4; + /** + * The value will apply to the marginEnd. + * @hide + */ + public static final int MARGIN_END = 5; /** @hide **/ @IntDef(flag = true, value = { @@ -283,7 +334,7 @@ public class RemoteViews implements Parcelable, Filter { /** * @hide */ - public void setRemoteInputs(int viewId, RemoteInput[] remoteInputs) { + public void setRemoteInputs(@IdRes int viewId, RemoteInput[] remoteInputs) { mActions.add(new SetRemoteInputsAction(viewId, remoteInputs)); } @@ -319,7 +370,7 @@ public class RemoteViews implements Parcelable, Filter { * * @hide */ - public void setIntTag(int viewId, int key, int tag) { + public void setIntTag(@IdRes int viewId, @IdRes int key, int tag) { addAction(new SetIntTagAction(viewId, key, tag)); } @@ -475,6 +526,7 @@ public class RemoteViews implements Parcelable, Filter { // Nothing to visit by default } + @IdRes @UnsupportedAppUsage int viewId; } @@ -599,7 +651,7 @@ public class RemoteViews implements Parcelable, Filter { private class SetEmptyView extends Action { int emptyViewId; - SetEmptyView(int viewId, int emptyViewId) { + SetEmptyView(@IdRes int viewId, @IdRes int emptyViewId) { this.viewId = viewId; this.emptyViewId = emptyViewId; } @@ -634,7 +686,7 @@ public class RemoteViews implements Parcelable, Filter { } private class SetPendingIntentTemplate extends Action { - public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) { + public SetPendingIntentTemplate(@IdRes int id, PendingIntent pendingIntentTemplate) { this.viewId = id; this.pendingIntentTemplate = pendingIntentTemplate; } @@ -705,7 +757,8 @@ public class RemoteViews implements Parcelable, Filter { } private class SetRemoteViewsAdapterList extends Action { - public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) { + public SetRemoteViewsAdapterList(@IdRes int id, ArrayList<RemoteViews> list, + int viewTypeCount) { this.viewId = id; this.list = list; this.viewTypeCount = viewTypeCount; @@ -770,7 +823,7 @@ public class RemoteViews implements Parcelable, Filter { } private class SetRemoteViewsAdapterIntent extends Action { - public SetRemoteViewsAdapterIntent(int id, Intent intent) { + public SetRemoteViewsAdapterIntent(@IdRes int id, Intent intent) { this.viewId = id; this.intent = intent; } @@ -845,7 +898,7 @@ public class RemoteViews implements Parcelable, Filter { */ private class SetOnClickResponse extends Action { - SetOnClickResponse(int id, RemoteResponse response) { + SetOnClickResponse(@IdRes int id, RemoteResponse response) { this.viewId = id; this.mResponse = response; } @@ -1007,8 +1060,8 @@ public class RemoteViews implements Parcelable, Filter { * <p> */ private class SetDrawableTint extends Action { - SetDrawableTint(int id, boolean targetBackground, - int colorFilter, @NonNull PorterDuff.Mode mode) { + SetDrawableTint(@IdRes int id, boolean targetBackground, + @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) { this.viewId = id; this.targetBackground = targetBackground; this.colorFilter = colorFilter; @@ -1054,7 +1107,7 @@ public class RemoteViews implements Parcelable, Filter { } boolean targetBackground; - int colorFilter; + @ColorInt int colorFilter; PorterDuff.Mode filterMode; } @@ -1071,7 +1124,7 @@ public class RemoteViews implements Parcelable, Filter { ColorStateList mColorStateList; - SetRippleDrawableColor(int id, ColorStateList colorStateList) { + SetRippleDrawableColor(@IdRes int id, ColorStateList colorStateList) { this.viewId = id; this.mColorStateList = colorStateList; } @@ -1108,7 +1161,7 @@ public class RemoteViews implements Parcelable, Filter { private final class ViewContentNavigation extends Action { final boolean mNext; - ViewContentNavigation(int viewId, boolean next) { + ViewContentNavigation(@IdRes int viewId, boolean next) { this.viewId = viewId; this.mNext = next; } @@ -1205,7 +1258,7 @@ public class RemoteViews implements Parcelable, Filter { @UnsupportedAppUsage String methodName; - BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) { + BitmapReflectionAction(@IdRes int viewId, String methodName, Bitmap bitmap) { this.bitmap = bitmap; this.viewId = viewId; this.methodName = methodName; @@ -1274,7 +1327,7 @@ public class RemoteViews implements Parcelable, Filter { @UnsupportedAppUsage Object value; - ReflectionAction(int viewId, String methodName, int type, Object value) { + ReflectionAction(@IdRes int viewId, String methodName, int type, Object value) { this.viewId = viewId; this.methodName = methodName; this.type = type; @@ -1568,11 +1621,11 @@ public class RemoteViews implements Parcelable, Filter { private RemoteViews mNestedViews; private int mIndex; - ViewGroupActionAdd(int viewId, RemoteViews nestedViews) { + ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews) { this(viewId, nestedViews, -1 /* index */); } - ViewGroupActionAdd(int viewId, RemoteViews nestedViews, int index) { + ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews, int index) { this.viewId = viewId; mNestedViews = nestedViews; mIndex = index; @@ -1682,11 +1735,11 @@ public class RemoteViews implements Parcelable, Filter { private int mViewIdToKeep; - ViewGroupActionRemove(int viewId) { + ViewGroupActionRemove(@IdRes int viewId) { this(viewId, REMOVE_ALL_VIEWS_ID); } - ViewGroupActionRemove(int viewId, int viewIdToKeep) { + ViewGroupActionRemove(@IdRes int viewId, @IdRes int viewIdToKeep) { this.viewId = viewId; mViewIdToKeep = viewIdToKeep; } @@ -1730,8 +1783,16 @@ public class RemoteViews implements Parcelable, Filter { final ViewGroup targetVg = (ViewGroup) target.mRoot; - // Clear all children when nested views omitted - target.mChildren = null; + if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { + // Clear all children when there's no excepted view + target.mChildren = null; + } else { + // Remove just the children which don't match the excepted view + target.mChildren.removeIf(childTree -> childTree.mRoot.getId() != mViewIdToKeep); + if (target.mChildren.isEmpty()) { + target.mChildren = null; + } + } return new RuntimeAction() { @Override public void apply(View root, ViewGroup rootParent, OnClickHandler handler) @@ -1773,11 +1834,81 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Action to remove a view from its parent. + */ + private class RemoveFromParentAction extends Action { + + RemoveFromParentAction(@IdRes int viewId) { + this.viewId = viewId; + } + + RemoveFromParentAction(Parcel parcel) { + viewId = parcel.readInt(); + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(viewId); + } + + @Override + public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { + final View target = root.findViewById(viewId); + + if (target == null || target == root) { + return; + } + + ViewParent parent = target.getParent(); + if (parent instanceof ViewManager) { + ((ViewManager) parent).removeView(target); + } + } + + @Override + public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { + // In the async implementation, update the view tree so that subsequent calls to + // findViewById return the correct view. + root.createTree(); + ViewTree target = root.findViewTreeById(viewId); + + if (target == null || target == root) { + return ACTION_NOOP; + } + + ViewTree parent = root.findViewTreeParentOf(target); + if (parent == null || !(parent.mRoot instanceof ViewManager)) { + return ACTION_NOOP; + } + final ViewManager parentVg = (ViewManager) parent.mRoot; + + parent.mChildren.remove(target); + return new RuntimeAction() { + @Override + public void apply(View root, ViewGroup rootParent, OnClickHandler handler) + throws ActionException { + parentVg.removeView(target.mRoot); + } + }; + } + + @Override + public int getActionTag() { + return REMOVE_FROM_PARENT_ACTION_TAG; + } + + @Override + public int mergeBehavior() { + return MERGE_APPEND; + } + } + + /** * Helper action to set compound drawables on a TextView. Supports relative * (s/t/e/b) or cardinal (l/t/r/b) arrangement. */ private class TextViewDrawableAction extends Action { - public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) { + public TextViewDrawableAction(@IdRes int viewId, boolean isRelative, @DrawableRes int d1, + @DrawableRes int d2, @DrawableRes int d3, @DrawableRes int d4) { this.viewId = viewId; this.isRelative = isRelative; this.useIcons = false; @@ -1787,7 +1918,7 @@ public class RemoteViews implements Parcelable, Filter { this.d4 = d4; } - public TextViewDrawableAction(int viewId, boolean isRelative, + public TextViewDrawableAction(@IdRes int viewId, boolean isRelative, Icon i1, Icon i2, Icon i3, Icon i4) { this.viewId = viewId; this.isRelative = isRelative; @@ -1922,13 +2053,13 @@ public class RemoteViews implements Parcelable, Filter { * Helper action to set text size on a TextView in any supported units. */ private class TextViewSizeAction extends Action { - public TextViewSizeAction(int viewId, int units, float size) { + TextViewSizeAction(@IdRes int viewId, @ComplexDimensionUnit int units, float size) { this.viewId = viewId; this.units = units; this.size = size; } - public TextViewSizeAction(Parcel parcel) { + TextViewSizeAction(Parcel parcel) { viewId = parcel.readInt(); units = parcel.readInt(); size = parcel.readFloat(); @@ -1960,7 +2091,8 @@ public class RemoteViews implements Parcelable, Filter { * Helper action to set padding on a View. */ private class ViewPaddingAction extends Action { - public ViewPaddingAction(int viewId, int left, int top, int right, int bottom) { + public ViewPaddingAction(@IdRes int viewId, @Px int left, @Px int top, + @Px int right, @Px int bottom) { this.viewId = viewId; this.left = left; this.top = top; @@ -1996,7 +2128,7 @@ public class RemoteViews implements Parcelable, Filter { return VIEW_PADDING_ACTION_TAG; } - int left, top, right, bottom; + @Px int left, top, right, bottom; } /** @@ -2004,36 +2136,56 @@ public class RemoteViews implements Parcelable, Filter { */ private static class LayoutParamAction extends Action { - /** Set marginEnd */ - public static final int LAYOUT_MARGIN_END_DIMEN = 1; - /** Set width */ - public static final int LAYOUT_WIDTH = 2; - public static final int LAYOUT_MARGIN_BOTTOM_DIMEN = 3; - public static final int LAYOUT_MARGIN_END = 4; + static final int LAYOUT_MARGIN_LEFT = MARGIN_LEFT; + static final int LAYOUT_MARGIN_TOP = MARGIN_TOP; + static final int LAYOUT_MARGIN_RIGHT = MARGIN_RIGHT; + static final int LAYOUT_MARGIN_BOTTOM = MARGIN_BOTTOM; + static final int LAYOUT_MARGIN_START = MARGIN_START; + static final int LAYOUT_MARGIN_END = MARGIN_END; + static final int LAYOUT_WIDTH = 8; + static final int LAYOUT_HEIGHT = 9; final int mProperty; + final boolean mIsDimen; final int mValue; /** * @param viewId ID of the view alter * @param property which layout parameter to alter * @param value new value of the layout parameter + * @param units the units of the given value + */ + LayoutParamAction(@IdRes int viewId, int property, float value, + @ComplexDimensionUnit int units) { + this.viewId = viewId; + this.mProperty = property; + this.mIsDimen = false; + this.mValue = TypedValue.createComplexDimension(value, units); + } + + /** + * @param viewId ID of the view alter + * @param property which layout parameter to alter + * @param dimen new dimension with the value of the layout parameter */ - public LayoutParamAction(int viewId, int property, int value) { + LayoutParamAction(@IdRes int viewId, int property, @DimenRes int dimen) { this.viewId = viewId; this.mProperty = property; - this.mValue = value; + this.mIsDimen = true; + this.mValue = dimen; } public LayoutParamAction(Parcel parcel) { viewId = parcel.readInt(); mProperty = parcel.readInt(); + mIsDimen = parcel.readBoolean(); mValue = parcel.readInt(); } public void writeToParcel(Parcel dest, int flags) { dest.writeInt(viewId); dest.writeInt(mProperty); + dest.writeBoolean(mIsDimen); dest.writeInt(mValue); } @@ -2047,26 +2199,49 @@ public class RemoteViews implements Parcelable, Filter { if (layoutParams == null) { return; } - int value = mValue; switch (mProperty) { - case LAYOUT_MARGIN_END_DIMEN: - value = resolveDimenPixelOffset(target, mValue); - // fall-through - case LAYOUT_MARGIN_END: - if (layoutParams instanceof ViewGroup.MarginLayoutParams) { - ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(value); + case LAYOUT_MARGIN_LEFT: + if (layoutParams instanceof MarginLayoutParams) { + ((MarginLayoutParams) layoutParams).leftMargin = getPixelOffset(target); + target.setLayoutParams(layoutParams); + } + break; + case LAYOUT_MARGIN_TOP: + if (layoutParams instanceof MarginLayoutParams) { + ((MarginLayoutParams) layoutParams).topMargin = getPixelOffset(target); target.setLayoutParams(layoutParams); } break; - case LAYOUT_MARGIN_BOTTOM_DIMEN: - if (layoutParams instanceof ViewGroup.MarginLayoutParams) { - int resolved = resolveDimenPixelOffset(target, mValue); - ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = resolved; + case LAYOUT_MARGIN_RIGHT: + if (layoutParams instanceof MarginLayoutParams) { + ((MarginLayoutParams) layoutParams).rightMargin = getPixelOffset(target); + target.setLayoutParams(layoutParams); + } + break; + case LAYOUT_MARGIN_BOTTOM: + if (layoutParams instanceof MarginLayoutParams) { + ((MarginLayoutParams) layoutParams).bottomMargin = getPixelOffset(target); + target.setLayoutParams(layoutParams); + } + break; + case LAYOUT_MARGIN_START: + if (layoutParams instanceof MarginLayoutParams) { + ((MarginLayoutParams) layoutParams).setMarginStart(getPixelOffset(target)); + target.setLayoutParams(layoutParams); + } + break; + case LAYOUT_MARGIN_END: + if (layoutParams instanceof MarginLayoutParams) { + ((MarginLayoutParams) layoutParams).setMarginEnd(getPixelOffset(target)); target.setLayoutParams(layoutParams); } break; case LAYOUT_WIDTH: - layoutParams.width = mValue; + layoutParams.width = getPixelSize(target); + target.setLayoutParams(layoutParams); + break; + case LAYOUT_HEIGHT: + layoutParams.height = getPixelSize(target); target.setLayoutParams(layoutParams); break; default: @@ -2074,11 +2249,26 @@ public class RemoteViews implements Parcelable, Filter { } } - private static int resolveDimenPixelOffset(View target, int value) { - if (value == 0) { - return 0; + private int getPixelOffset(View target) { + if (mIsDimen) { + if (mValue == 0) { + return 0; + } + return target.getResources().getDimensionPixelOffset(mValue); } - return target.getContext().getResources().getDimensionPixelOffset(value); + return TypedValue.complexToDimensionPixelOffset(mValue, + target.getResources().getDisplayMetrics()); + } + + private int getPixelSize(View target) { + if (mIsDimen) { + if (mValue == 0) { + return 0; + } + return target.getResources().getDimensionPixelSize(mValue); + } + return TypedValue.complexToDimensionPixelSize(mValue, + target.getResources().getDisplayMetrics()); } @Override @@ -2097,7 +2287,7 @@ public class RemoteViews implements Parcelable, Filter { */ private class SetRemoteInputsAction extends Action { - public SetRemoteInputsAction(int viewId, RemoteInput[] remoteInputs) { + public SetRemoteInputsAction(@IdRes int viewId, RemoteInput[] remoteInputs) { this.viewId = viewId; this.remoteInputs = remoteInputs; } @@ -2175,11 +2365,11 @@ public class RemoteViews implements Parcelable, Filter { } private class SetIntTagAction extends Action { - private final int mViewId; - private final int mKey; + @IdRes private final int mViewId; + @IdRes private final int mKey; private final int mTag; - SetIntTagAction(int viewId, int key, int tag) { + SetIntTagAction(@IdRes int viewId, @IdRes int key, int tag) { mViewId = viewId; mKey = key; mTag = tag; @@ -2419,6 +2609,8 @@ public class RemoteViews implements Parcelable, Filter { return new SetRippleDrawableColor(parcel); case SET_INT_TAG_TAG: return new SetIntTagAction(parcel); + case REMOVE_FROM_PARENT_ACTION_TAG: + return new RemoveFromParentAction(parcel); default: throw new ActionException("Tag " + tag + " not found"); } @@ -2511,7 +2703,8 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the parent {@link ViewGroup} to add child into. * @param nestedView {@link RemoteViews} that describes the child. */ - public void addView(int viewId, RemoteViews nestedView) { + public void addView(@IdRes int viewId, RemoteViews nestedView) { + // Clear all children when nested views omitted addAction(nestedView == null ? new ViewGroupActionRemove(viewId) : new ViewGroupActionAdd(viewId, nestedView)); @@ -2528,7 +2721,7 @@ public class RemoteViews implements Parcelable, Filter { * @hide */ @UnsupportedAppUsage - public void addView(int viewId, RemoteViews nestedView, int index) { + public void addView(@IdRes int viewId, RemoteViews nestedView, int index) { addAction(new ViewGroupActionAdd(viewId, nestedView, index)); } @@ -2538,7 +2731,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the parent {@link ViewGroup} to remove all * children from. */ - public void removeAllViews(int viewId) { + public void removeAllViews(@IdRes int viewId) { addAction(new ViewGroupActionRemove(viewId)); } @@ -2551,16 +2744,28 @@ public class RemoteViews implements Parcelable, Filter { * * @hide */ - public void removeAllViewsExceptId(int viewId, int viewIdToKeep) { + public void removeAllViewsExceptId(@IdRes int viewId, @IdRes int viewIdToKeep) { addAction(new ViewGroupActionRemove(viewId, viewIdToKeep)); } /** + * Removes the {@link View} specified by the {@code viewId} from its parent {@link ViewManager}. + * This will do nothing if the viewId specifies the root view of this RemoteViews. + * + * @param viewId The id of the {@link View} to remove from its parent. + * + * @hide + */ + public void removeFromParent(@IdRes int viewId) { + addAction(new RemoveFromParentAction(viewId)); + } + + /** * Equivalent to calling {@link AdapterViewAnimator#showNext()} * * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()} */ - public void showNext(int viewId) { + public void showNext(@IdRes int viewId) { addAction(new ViewContentNavigation(viewId, true /* next */)); } @@ -2569,7 +2774,7 @@ public class RemoteViews implements Parcelable, Filter { * * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()} */ - public void showPrevious(int viewId) { + public void showPrevious(@IdRes int viewId) { addAction(new ViewContentNavigation(viewId, false /* next */)); } @@ -2579,7 +2784,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view on which to call * {@link AdapterViewAnimator#setDisplayedChild(int)} */ - public void setDisplayedChild(int viewId, int childIndex) { + public void setDisplayedChild(@IdRes int viewId, int childIndex) { setInt(viewId, "setDisplayedChild", childIndex); } @@ -2589,7 +2794,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose visibility should change * @param visibility The new visibility for the view */ - public void setViewVisibility(int viewId, int visibility) { + public void setViewVisibility(@IdRes int viewId, @View.Visibility int visibility) { setInt(viewId, "setVisibility", visibility); } @@ -2599,7 +2804,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose text should change * @param text The new text for the view */ - public void setTextViewText(int viewId, CharSequence text) { + public void setTextViewText(@IdRes int viewId, CharSequence text) { setCharSequence(viewId, "setText", text); } @@ -2610,7 +2815,7 @@ public class RemoteViews implements Parcelable, Filter { * @param units The units of size (e.g. COMPLEX_UNIT_SP) * @param size The size of the text */ - public void setTextViewTextSize(int viewId, int units, float size) { + public void setTextViewTextSize(@IdRes int viewId, int units, float size) { addAction(new TextViewSizeAction(viewId, units, size)); } @@ -2624,7 +2829,8 @@ public class RemoteViews implements Parcelable, Filter { * @param right The id of a drawable to place to the right of the text, or 0 * @param bottom The id of a drawable to place below the text, or 0 */ - public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) { + public void setTextViewCompoundDrawables(@IdRes int viewId, @DrawableRes int left, + @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); } @@ -2639,7 +2845,8 @@ public class RemoteViews implements Parcelable, Filter { * @param end The id of a drawable to place after the text, or 0 * @param bottom The id of a drawable to place below the text, or 0 */ - public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) { + public void setTextViewCompoundDrawablesRelative(@IdRes int viewId, @DrawableRes int start, + @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); } @@ -2656,7 +2863,8 @@ public class RemoteViews implements Parcelable, Filter { * * @hide */ - public void setTextViewCompoundDrawables(int viewId, Icon left, Icon top, Icon right, Icon bottom) { + public void setTextViewCompoundDrawables(@IdRes int viewId, + Icon left, Icon top, Icon right, Icon bottom) { addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); } @@ -2674,7 +2882,8 @@ public class RemoteViews implements Parcelable, Filter { * * @hide */ - public void setTextViewCompoundDrawablesRelative(int viewId, Icon start, Icon top, Icon end, Icon bottom) { + public void setTextViewCompoundDrawablesRelative(@IdRes int viewId, + Icon start, Icon top, Icon end, Icon bottom) { addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); } @@ -2684,7 +2893,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose drawable should change * @param srcId The new resource id for the drawable */ - public void setImageViewResource(int viewId, int srcId) { + public void setImageViewResource(@IdRes int viewId, @DrawableRes int srcId) { setInt(viewId, "setImageResource", srcId); } @@ -2694,7 +2903,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose drawable should change * @param uri The Uri for the image */ - public void setImageViewUri(int viewId, Uri uri) { + public void setImageViewUri(@IdRes int viewId, Uri uri) { setUri(viewId, "setImageURI", uri); } @@ -2704,7 +2913,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose bitmap should change * @param bitmap The new Bitmap for the drawable */ - public void setImageViewBitmap(int viewId, Bitmap bitmap) { + public void setImageViewBitmap(@IdRes int viewId, Bitmap bitmap) { setBitmap(viewId, "setImageBitmap", bitmap); } @@ -2714,7 +2923,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose bitmap should change * @param icon The new Icon for the ImageView */ - public void setImageViewIcon(int viewId, Icon icon) { + public void setImageViewIcon(@IdRes int viewId, Icon icon) { setIcon(viewId, "setImageIcon", icon); } @@ -2724,7 +2933,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view on which to set the empty view * @param emptyViewId The view id of the empty view */ - public void setEmptyView(int viewId, int emptyViewId) { + public void setEmptyView(@IdRes int viewId, @IdRes int emptyViewId) { addAction(new SetEmptyView(viewId, emptyViewId)); } @@ -2744,7 +2953,7 @@ public class RemoteViews implements Parcelable, Filter { * * @see #setChronometerCountDown(int, boolean) */ - public void setChronometer(int viewId, long base, String format, boolean started) { + public void setChronometer(@IdRes int viewId, long base, String format, boolean started) { setLong(viewId, "setBase", base); setString(viewId, "setFormat", format); setBoolean(viewId, "setStarted", started); @@ -2758,7 +2967,7 @@ public class RemoteViews implements Parcelable, Filter { * @param isCountDown True if you want the chronometer to count down to base instead of * counting up. */ - public void setChronometerCountDown(int viewId, boolean isCountDown) { + public void setChronometerCountDown(@IdRes int viewId, boolean isCountDown) { setBoolean(viewId, "setCountDown", isCountDown); } @@ -2775,7 +2984,7 @@ public class RemoteViews implements Parcelable, Filter { * @param indeterminate True if the progress bar is indeterminate, * false if not. */ - public void setProgressBar(int viewId, int max, int progress, + public void setProgressBar(@IdRes int viewId, int max, int progress, boolean indeterminate) { setBoolean(viewId, "setIndeterminate", indeterminate); if (!indeterminate) { @@ -2801,7 +3010,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked * @param pendingIntent The {@link PendingIntent} to send when user clicks */ - public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) { + public void setOnClickPendingIntent(@IdRes int viewId, PendingIntent pendingIntent) { setOnClickResponse(viewId, RemoteResponse.fromPendingIntent(pendingIntent)); } @@ -2813,7 +3022,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view that will trigger the {@link RemoteResponse} when clicked * @param response The {@link RemoteResponse} to send when user clicks */ - public void setOnClickResponse(int viewId, @NonNull RemoteResponse response) { + public void setOnClickResponse(@IdRes int viewId, @NonNull RemoteResponse response) { addAction(new SetOnClickResponse(viewId, response)); } @@ -2829,7 +3038,7 @@ public class RemoteViews implements Parcelable, Filter { * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified * by a child of viewId and executed when that child is clicked */ - public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) { + public void setPendingIntentTemplate(@IdRes int viewId, PendingIntent pendingIntentTemplate) { addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate)); } @@ -2850,7 +3059,7 @@ public class RemoteViews implements Parcelable, Filter { * @param fillInIntent The intent which will be combined with the parent's PendingIntent * in order to determine the on-click behavior of the view specified by viewId */ - public void setOnClickFillInIntent(int viewId, Intent fillInIntent) { + public void setOnClickFillInIntent(@IdRes int viewId, Intent fillInIntent) { setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent)); } @@ -2874,8 +3083,8 @@ public class RemoteViews implements Parcelable, Filter { * @param mode Specify a PorterDuff mode for this drawable, or null to leave * unchanged. */ - public void setDrawableTint(int viewId, boolean targetBackground, - int colorFilter, @NonNull PorterDuff.Mode mode) { + public void setDrawableTint(@IdRes int viewId, boolean targetBackground, + @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) { addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode)); } @@ -2891,7 +3100,7 @@ public class RemoteViews implements Parcelable, Filter { * @param colorStateList Specify a color for a * {@link ColorStateList} for this drawable. */ - public void setRippleDrawableColor(int viewId, ColorStateList colorStateList) { + public void setRippleDrawableColor(@IdRes int viewId, ColorStateList colorStateList) { addAction(new SetRippleDrawableColor(viewId, colorStateList)); } @@ -2902,7 +3111,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose tint should change * @param tint the tint to apply, may be {@code null} to clear tint */ - public void setProgressTintList(int viewId, ColorStateList tint) { + public void setProgressTintList(@IdRes int viewId, ColorStateList tint) { addAction(new ReflectionAction(viewId, "setProgressTintList", ReflectionAction.COLOR_STATE_LIST, tint)); } @@ -2914,7 +3123,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose tint should change * @param tint the tint to apply, may be {@code null} to clear tint */ - public void setProgressBackgroundTintList(int viewId, ColorStateList tint) { + public void setProgressBackgroundTintList(@IdRes int viewId, ColorStateList tint) { addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList", ReflectionAction.COLOR_STATE_LIST, tint)); } @@ -2926,7 +3135,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose tint should change * @param tint the tint to apply, may be {@code null} to clear tint */ - public void setProgressIndeterminateTintList(int viewId, ColorStateList tint) { + public void setProgressIndeterminateTintList(@IdRes int viewId, ColorStateList tint) { addAction(new ReflectionAction(viewId, "setIndeterminateTintList", ReflectionAction.COLOR_STATE_LIST, tint)); } @@ -2938,7 +3147,7 @@ public class RemoteViews implements Parcelable, Filter { * @param color Sets the text color for all the states (normal, selected, * focused) to be this color. */ - public void setTextColor(int viewId, @ColorInt int color) { + public void setTextColor(@IdRes int viewId, @ColorInt int color) { setInt(viewId, "setTextColor", color); } @@ -2949,7 +3158,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose text color should change * @param colors the text colors to set */ - public void setTextColor(int viewId, @ColorInt ColorStateList colors) { + public void setTextColor(@IdRes int viewId, ColorStateList colors) { addAction(new ReflectionAction(viewId, "setTextColor", ReflectionAction.COLOR_STATE_LIST, colors)); } @@ -2966,7 +3175,7 @@ public class RemoteViews implements Parcelable, Filter { * {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)} */ @Deprecated - public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) { + public void setRemoteAdapter(int appWidgetId, @IdRes int viewId, Intent intent) { setRemoteAdapter(viewId, intent); } @@ -2978,7 +3187,7 @@ public class RemoteViews implements Parcelable, Filter { * @param intent The intent of the service which will be * providing data to the RemoteViewsAdapter */ - public void setRemoteAdapter(int viewId, Intent intent) { + public void setRemoteAdapter(@IdRes int viewId, Intent intent) { addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); } @@ -3006,7 +3215,8 @@ public class RemoteViews implements Parcelable, Filter { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @Deprecated - public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) { + public void setRemoteAdapter(@IdRes int viewId, ArrayList<RemoteViews> list, + int viewTypeCount) { addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount)); } @@ -3016,7 +3226,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view to change * @param position Scroll to this adapter position */ - public void setScrollPosition(int viewId, int position) { + public void setScrollPosition(@IdRes int viewId, int position) { setInt(viewId, "smoothScrollToPosition", position); } @@ -3026,7 +3236,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view to change * @param offset Scroll by this adapter position offset */ - public void setRelativeScrollPosition(int viewId, int offset) { + public void setRelativeScrollPosition(@IdRes int viewId, int offset) { setInt(viewId, "smoothScrollByOffset", offset); } @@ -3039,62 +3249,100 @@ public class RemoteViews implements Parcelable, Filter { * @param right the right padding in pixels * @param bottom the bottom padding in pixels */ - public void setViewPadding(int viewId, int left, int top, int right, int bottom) { + public void setViewPadding(@IdRes int viewId, + @Px int left, @Px int top, @Px int right, @Px int bottom) { addAction(new ViewPaddingAction(viewId, left, top, right, bottom)); } /** - * @hide - * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}. + * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}. * Only works if the {@link View#getLayoutParams()} supports margins. - * Hidden for now since we don't want to support this for all different layout margins yet. * * @param viewId The id of the view to change - * @param endMarginDimen a dimen resource to read the margin from or 0 to clear the margin. + * @param type The margin being set e.g. {@link #MARGIN_END} + * @param dimen a dimension resource to apply to the margin, or 0 to clear the margin. + * @hide */ - public void setViewLayoutMarginEndDimen(int viewId, @DimenRes int endMarginDimen) { - addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END_DIMEN, - endMarginDimen)); + public void setViewLayoutMarginDimen(@IdRes int viewId, @MarginType int type, + @DimenRes int dimen) { + addAction(new LayoutParamAction(viewId, type, dimen)); } /** - * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}. + * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}. * Only works if the {@link View#getLayoutParams()} supports margins. - * Hidden for now since we don't want to support this for all different layout margins yet. + * + * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0. + * Setting margins in pixels will behave poorly when the RemoteViews object is used on a + * display with a different density. * * @param viewId The id of the view to change - * @param endMargin a value in pixels for the end margin. + * @param type The margin being set e.g. {@link #MARGIN_END} + * @param value a value for the margin the given units. + * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} * @hide */ - public void setViewLayoutMarginEnd(int viewId, @DimenRes int endMargin) { - addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END, - endMargin)); + public void setViewLayoutMargin(@IdRes int viewId, @MarginType int type, float value, + @ComplexDimensionUnit int units) { + addAction(new LayoutParamAction(viewId, type, value, units)); } /** - * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}. + * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} except that you may + * provide the value in any dimension units. * - * @param bottomMarginDimen a dimen resource to read the margin from or 0 to clear the margin. + * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0, + * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}. + * Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a + * display with a different density. + * + * @param width Width of the view in the given units + * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} * @hide */ - public void setViewLayoutMarginBottomDimen(int viewId, @DimenRes int bottomMarginDimen) { - addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM_DIMEN, - bottomMarginDimen)); + public void setViewLayoutWidth(@IdRes int viewId, float width, + @ComplexDimensionUnit int units) { + addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, width, units)); } /** - * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}. + * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} with + * the result of {@link Resources#getDimensionPixelSize(int)}. * - * @param layoutWidth one of 0, MATCH_PARENT or WRAP_CONTENT. Other sizes are not allowed - * because they behave poorly when the density changes. + * @param widthDimen the dimension resource for the view's width * @hide */ - public void setViewLayoutWidth(int viewId, int layoutWidth) { - if (layoutWidth != 0 && layoutWidth != ViewGroup.LayoutParams.MATCH_PARENT - && layoutWidth != ViewGroup.LayoutParams.WRAP_CONTENT) { - throw new IllegalArgumentException("Only supports 0, WRAP_CONTENT and MATCH_PARENT"); - } - mActions.add(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, layoutWidth)); + public void setViewLayoutWidthDimen(@IdRes int viewId, @DimenRes int widthDimen) { + addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, widthDimen)); + } + + /** + * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} except that you may + * provide the value in any dimension units. + * + * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0, + * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}. + * Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a + * display with a different density. + * + * @param height height of the view in the given units + * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} + * @hide + */ + public void setViewLayoutHeight(@IdRes int viewId, float height, + @ComplexDimensionUnit int units) { + addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, height, units)); + } + + /** + * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} with + * the result of {@link Resources#getDimensionPixelSize(int)}. + * + * @param heightDimen a dimen resource to read the height from. + * @hide + */ + public void setViewLayoutHeightDimen(@IdRes int viewId, @DimenRes int heightDimen) { + addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, heightDimen)); } /** @@ -3104,7 +3352,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setBoolean(int viewId, String methodName, boolean value) { + public void setBoolean(@IdRes int viewId, String methodName, boolean value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value)); } @@ -3115,7 +3363,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setByte(int viewId, String methodName, byte value) { + public void setByte(@IdRes int viewId, String methodName, byte value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value)); } @@ -3126,7 +3374,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setShort(int viewId, String methodName, short value) { + public void setShort(@IdRes int viewId, String methodName, short value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value)); } @@ -3137,7 +3385,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setInt(int viewId, String methodName, int value) { + public void setInt(@IdRes int viewId, String methodName, int value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value)); } @@ -3150,7 +3398,7 @@ public class RemoteViews implements Parcelable, Filter { * * @hide */ - public void setColorStateList(int viewId, String methodName, ColorStateList value) { + public void setColorStateList(@IdRes int viewId, String methodName, ColorStateList value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.COLOR_STATE_LIST, value)); } @@ -3163,7 +3411,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setLong(int viewId, String methodName, long value) { + public void setLong(@IdRes int viewId, String methodName, long value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value)); } @@ -3174,7 +3422,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setFloat(int viewId, String methodName, float value) { + public void setFloat(@IdRes int viewId, String methodName, float value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value)); } @@ -3185,7 +3433,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setDouble(int viewId, String methodName, double value) { + public void setDouble(@IdRes int viewId, String methodName, double value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value)); } @@ -3196,7 +3444,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setChar(int viewId, String methodName, char value) { + public void setChar(@IdRes int viewId, String methodName, char value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value)); } @@ -3207,7 +3455,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setString(int viewId, String methodName, String value) { + public void setString(@IdRes int viewId, String methodName, String value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value)); } @@ -3218,7 +3466,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setCharSequence(int viewId, String methodName, CharSequence value) { + public void setCharSequence(@IdRes int viewId, String methodName, CharSequence value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value)); } @@ -3229,7 +3477,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setUri(int viewId, String methodName, Uri value) { + public void setUri(@IdRes int viewId, String methodName, Uri value) { if (value != null) { // Resolve any filesystem path before sending remotely value = value.getCanonicalUri(); @@ -3250,7 +3498,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setBitmap(int viewId, String methodName, Bitmap value) { + public void setBitmap(@IdRes int viewId, String methodName, Bitmap value) { addAction(new BitmapReflectionAction(viewId, methodName, value)); } @@ -3261,7 +3509,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The value to pass to the method. */ - public void setBundle(int viewId, String methodName, Bundle value) { + public void setBundle(@IdRes int viewId, String methodName, Bundle value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value)); } @@ -3272,7 +3520,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The {@link android.content.Intent} to pass the method. */ - public void setIntent(int viewId, String methodName, Intent value) { + public void setIntent(@IdRes int viewId, String methodName, Intent value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value)); } @@ -3283,7 +3531,7 @@ public class RemoteViews implements Parcelable, Filter { * @param methodName The name of the method to call. * @param value The {@link android.graphics.drawable.Icon} to pass the method. */ - public void setIcon(int viewId, String methodName, Icon value) { + public void setIcon(@IdRes int viewId, String methodName, Icon value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.ICON, value)); } @@ -3293,7 +3541,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose content description should change. * @param contentDescription The new content description for the view. */ - public void setContentDescription(int viewId, CharSequence contentDescription) { + public void setContentDescription(@IdRes int viewId, CharSequence contentDescription) { setCharSequence(viewId, "setContentDescription", contentDescription); } @@ -3303,7 +3551,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose before view in accessibility traversal to set. * @param nextId The id of the next in the accessibility traversal. **/ - public void setAccessibilityTraversalBefore(int viewId, int nextId) { + public void setAccessibilityTraversalBefore(@IdRes int viewId, @IdRes int nextId) { setInt(viewId, "setAccessibilityTraversalBefore", nextId); } @@ -3313,7 +3561,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose after view in accessibility traversal to set. * @param nextId The id of the next in the accessibility traversal. **/ - public void setAccessibilityTraversalAfter(int viewId, int nextId) { + public void setAccessibilityTraversalAfter(@IdRes int viewId, @IdRes int nextId) { setInt(viewId, "setAccessibilityTraversalAfter", nextId); } @@ -3323,7 +3571,7 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view whose property to set. * @param labeledId The id of a view for which this view serves as a label. */ - public void setLabelFor(int viewId, int labeledId) { + public void setLabelFor(@IdRes int viewId, @IdRes int labeledId) { setInt(viewId, "setLabelFor", labeledId); } @@ -3847,7 +4095,7 @@ public class RemoteViews implements Parcelable, Filter { } } - public ViewTree findViewTreeById(int id) { + public ViewTree findViewTreeById(@IdRes int id) { if (mRoot.getId() == id) { return this; } @@ -3863,13 +4111,29 @@ public class RemoteViews implements Parcelable, Filter { return null; } + public ViewTree findViewTreeParentOf(ViewTree child) { + if (mChildren == null) { + return null; + } + for (ViewTree tree : mChildren) { + if (tree == child) { + return this; + } + ViewTree result = tree.findViewTreeParentOf(child); + if (result != null) { + return result; + } + } + return null; + } + public void replaceView(View v) { mRoot = v; mChildren = null; createTree(); } - public <T extends View> T findViewById(int id) { + public <T extends View> T findViewById(@IdRes int id) { if (mChildren == null) { return mRoot.findViewById(id); } @@ -4003,7 +4267,8 @@ public class RemoteViews implements Parcelable, Filter { * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[]) */ @NonNull - public RemoteResponse addSharedElement(int viewId, @NonNull String sharedElementName) { + public RemoteResponse addSharedElement(@IdRes int viewId, + @NonNull String sharedElementName) { if (mViewIds == null) { mViewIds = new IntArray(); mElementNames = new ArrayList<>(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 4edfc5f309b2..b8660255acc1 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -494,6 +494,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private TextUtils.TruncateAt mEllipsize; + // A flag to indicate the cursor was hidden by IME. + private boolean mImeTemporarilyConsumesInput; + + // Whether cursor is visible without regard to {@link mImeTemporarilyConsumesInput}. + // {code true} is the default value. + private boolean mCursorVisibleFromAttr = true; + static class Drawables { static final int LEFT = 0; static final int TOP = 1; @@ -10496,7 +10503,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Set whether the cursor is visible. The default is true. Note that this property only - * makes sense for editable TextView. + * makes sense for editable TextView. If IME is temporarily consuming the input, the cursor will + * be always invisible, visibility will be updated as the last state when IME does not consume + * the input anymore. * * @see #isCursorVisible() * @@ -10504,6 +10513,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @android.view.RemotableViewMethod public void setCursorVisible(boolean visible) { + mCursorVisibleFromAttr = visible; + updateCursorVisibleInternal(); + } + + /** + * Sets the IME is temporarily consuming the input and make the cursor invisible if + * {@code imeTemporarilyConsumesInput} is {@code true}. Otherwise, make the cursor visible. + * + * @param imeTemporarilyConsumesInput {@code true} if IME is temporarily consuming the input + * + * @hide + */ + public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) { + mImeTemporarilyConsumesInput = imeTemporarilyConsumesInput; + updateCursorVisibleInternal(); + } + + private void updateCursorVisibleInternal() { + boolean visible = mCursorVisibleFromAttr && !mImeTemporarilyConsumesInput; if (visible && mEditor == null) return; // visible is the default value with no edit data createEditorIfNeeded(); if (mEditor.mCursorVisible != visible) { @@ -10518,7 +10546,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * @return whether or not the cursor is visible (assuming this TextView is editable) + * @return whether or not the cursor is visible (assuming this TextView is editable). This + * method may return {@code false} when the IME is temporarily consuming the input even if the + * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)} + * is called. * * @see #setCursorVisible(boolean) * @@ -11686,6 +11717,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } + String[] mimeTypes = getOnReceiveContentMimeTypes(); + if (mimeTypes == null && mEditor != null) { + // If the app hasn't set a listener for receiving content on this view (ie, + // getOnReceiveContentMimeTypes() returns null), check if it implements the + // keyboard image API and, if possible, use those MIME types as fallback. + // This fallback is only in place for autofill, not other mechanisms for + // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER + // in TextViewOnReceiveContentListener for more info. + mimeTypes = mEditor.getDefaultOnReceiveContentListener() + .getFallbackMimeTypesForAutofill(this); + } + structure.setOnReceiveContentMimeTypes(mimeTypes); } if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL diff --git a/core/java/android/widget/TextViewOnReceiveContentListener.java b/core/java/android/widget/TextViewOnReceiveContentListener.java index 8cef1061c423..91fac5511807 100644 --- a/core/java/android/widget/TextViewOnReceiveContentListener.java +++ b/core/java/android/widget/TextViewOnReceiveContentListener.java @@ -60,7 +60,7 @@ import java.util.Arrays; * * @hide */ -@VisibleForTesting +@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final class TextViewOnReceiveContentListener implements OnReceiveContentListener { private static final String LOG_TAG = "ReceiveContent"; @@ -261,10 +261,17 @@ public final class TextViewOnReceiveContentListener implements OnReceiveContentL mInputConnectionInfo = null; } - /** @hide */ - @VisibleForTesting + /** + * Returns the MIME types accepted by {@link View#performReceiveContent} for the given view, + * <strong>for autofill purposes</strong>. This will be non-null only if fallback to the + * keyboard image API {@link #isUsageOfImeCommitContentEnabled is enabled} and the view has an + * {@link InputConnection} with {@link EditorInfo#contentMimeTypes} populated. + * + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @Nullable - public String[] getEditorInfoMimeTypes(@NonNull TextView view) { + public String[] getFallbackMimeTypesForAutofill(@NonNull TextView view) { if (!isUsageOfImeCommitContentEnabled(view)) { return null; } diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java index 1254647379b5..878439906de2 100644 --- a/core/java/android/window/DisplayAreaOrganizer.java +++ b/core/java/android/window/DisplayAreaOrganizer.java @@ -92,6 +92,14 @@ public class DisplayAreaOrganizer extends WindowOrganizer { public static final int FEATURE_IME_PLACEHOLDER = FEATURE_SYSTEM_FIRST + 7; /** + * Display area for one handed background layer, which preventing when user + * turning the Dark theme on, they can not clearly identify the screen has entered + * one handed mode. + * @hide + */ + public static final int FEATURE_ONE_HANDED_BACKGROUND_PANEL = FEATURE_SYSTEM_FIRST + 8; + + /** * The last boundary of display area for system features */ public static final int FEATURE_SYSTEM_LAST = 10_000; diff --git a/core/java/android/window/ITransitionPlayer.aidl b/core/java/android/window/ITransitionPlayer.aidl index a8a29b26a148..55d47cb7ed8b 100644 --- a/core/java/android/window/ITransitionPlayer.aidl +++ b/core/java/android/window/ITransitionPlayer.aidl @@ -16,6 +16,7 @@ package android.window; +import android.app.ActivityManager; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerTransaction; @@ -58,6 +59,9 @@ oneway interface ITransitionPlayer { * @param type The {@link WindowManager#TransitionType} of the transition to start. * @param transitionToken An identifying token for the transition that needs to be started. * Pass this to {@link IWindowOrganizerController#startTransition}. + * @param triggerTask If non-null, the task containing the activity whose lifecycle change + * (start or finish) has caused this transition to occur. */ - void requestStartTransition(int type, in IBinder transitionToken); + void requestStartTransition(int type, in IBinder transitionToken, + in ActivityManager.RunningTaskInfo triggerTask); } diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index da291cf0fd2c..d1d49b6d4722 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -26,6 +26,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.graphics.Point; import android.graphics.Rect; import android.os.Parcel; @@ -153,7 +154,8 @@ public final class TransitionInfo implements Parcelable { /** * @return a surfacecontrol that can serve as a parent surfacecontrol for all the changing * participants to animate within. This will generally be placed at the highest-z-order - * shared ancestor of all participants. + * shared ancestor of all participants. While this is non-null, it's possible for the rootleash + * to be invalid if the transition is a no-op. */ @NonNull public SurfaceControl getRootLeash() { @@ -181,7 +183,7 @@ public final class TransitionInfo implements Parcelable { @Nullable public Change getChange(@NonNull WindowContainerToken token) { for (int i = mChanges.size() - 1; i >= 0; --i) { - if (mChanges.get(i).mContainer == token) { + if (token.equals(mChanges.get(i).mContainer)) { return mChanges.get(i); } } @@ -254,6 +256,7 @@ public final class TransitionInfo implements Parcelable { private final Rect mStartAbsBounds = new Rect(); private final Rect mEndAbsBounds = new Rect(); private final Point mEndRelOffset = new Point(); + private ActivityManager.RunningTaskInfo mTaskInfo = null; public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) { mContainer = container; @@ -270,6 +273,7 @@ public final class TransitionInfo implements Parcelable { mStartAbsBounds.readFromParcel(in); mEndAbsBounds.readFromParcel(in); mEndRelOffset.readFromParcel(in); + mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); } /** Sets the parent of this change's container. The parent must be a participant or null. */ @@ -302,6 +306,14 @@ public final class TransitionInfo implements Parcelable { mEndRelOffset.set(left, top); } + /** + * Sets the taskinfo of this container if this is a task. WARNING: this takes the + * reference, so don't modify it afterwards. + */ + public void setTaskInfo(ActivityManager.RunningTaskInfo taskInfo) { + mTaskInfo = taskInfo; + } + /** @return the container that is changing. May be null if non-remotable (eg. activity) */ @Nullable public WindowContainerToken getContainer() { @@ -359,6 +371,12 @@ public final class TransitionInfo implements Parcelable { return mLeash; } + /** @return the task info or null if this isn't a task */ + @NonNull + public ActivityManager.RunningTaskInfo getTaskInfo() { + return mTaskInfo; + } + @Override /** @hide */ public void writeToParcel(@NonNull Parcel dest, int flags) { @@ -370,6 +388,7 @@ public final class TransitionInfo implements Parcelable { mStartAbsBounds.writeToParcel(dest, flags); mEndAbsBounds.writeToParcel(dest, flags); mEndRelOffset.writeToParcel(dest, flags); + dest.writeTypedObject(mTaskInfo, flags); } @NonNull diff --git a/core/java/com/android/internal/compat/CompatibilityChangeInfo.java b/core/java/com/android/internal/compat/CompatibilityChangeInfo.java index 670ca9f6091e..03fe4551c249 100644 --- a/core/java/com/android/internal/compat/CompatibilityChangeInfo.java +++ b/core/java/com/android/internal/compat/CompatibilityChangeInfo.java @@ -32,6 +32,7 @@ public class CompatibilityChangeInfo implements Parcelable { 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 @@ public class CompatibilityChangeInfo implements Parcelable { 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 @@ public class CompatibilityChangeInfo implements Parcelable { this.mDisabled = disabled; this.mLoggingOnly = loggingOnly; this.mDescription = description; + this.mOverridable = overridable; } public CompatibilityChangeInfo(CompatibilityChangeInfo other) { @@ -84,6 +90,7 @@ public class CompatibilityChangeInfo implements Parcelable { this.mDisabled = other.mDisabled; this.mLoggingOnly = other.mLoggingOnly; this.mDescription = other.mDescription; + this.mOverridable = other.mOverridable; } private CompatibilityChangeInfo(Parcel in) { @@ -93,6 +100,7 @@ public class CompatibilityChangeInfo implements Parcelable { mDisabled = in.readBoolean(); mLoggingOnly = in.readBoolean(); mDescription = in.readString(); + mOverridable = in.readBoolean(); } @Override @@ -108,6 +116,7 @@ public class CompatibilityChangeInfo implements Parcelable { dest.writeBoolean(mDisabled); dest.writeBoolean(mLoggingOnly); dest.writeString(mDescription); + dest.writeBoolean(mOverridable); } @Override @@ -126,6 +135,9 @@ public class CompatibilityChangeInfo implements Parcelable { if (getLoggingOnly()) { sb.append("; loggingOnly"); } + if (getOverridable()) { + sb.append("; overridable"); + } return sb.append(")").toString(); } @@ -143,8 +155,8 @@ public class CompatibilityChangeInfo implements Parcelable { && 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/CallbackUtils.java b/core/java/com/android/internal/inputmethod/CallbackUtils.java index 248feb8bcbd7..e9e39db90437 100644 --- a/core/java/com/android/internal/inputmethod/CallbackUtils.java +++ b/core/java/com/android/internal/inputmethod/CallbackUtils.java @@ -200,4 +200,29 @@ public final class CallbackUtils { callback.onResult(result); } catch (RemoteException ignored) { } } + + /** + * A utility method using given {@link IVoidResultCallback} to callback the result. + * + * @param callback {@link IVoidResultCallback} to be called back. + * @param resultSupplier the supplier from which the result is provided. + */ + public static void onResult(@NonNull IVoidResultCallback callback, + @NonNull Supplier<Void> resultSupplier) { + Throwable exception = null; + + try { + resultSupplier.get(); + } catch (Throwable throwable) { + exception = throwable; + } + + try { + if (exception != null) { + callback.onError(ThrowableHolder.of(exception)); + return; + } + callback.onResult(); + } catch (RemoteException ignored) { } + } } diff --git a/core/java/com/android/internal/inputmethod/Completable.java b/core/java/com/android/internal/inputmethod/Completable.java index 1913fcdc9ba9..d6a466316ed4 100644 --- a/core/java/com/android/internal/inputmethod/Completable.java +++ b/core/java/com/android/internal/inputmethod/Completable.java @@ -117,8 +117,8 @@ public final class Completable { } /** - * @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 @@ public final class Completable { } /** - * 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(); + } } } @@ -286,6 +298,42 @@ public final class Completable { } /** + * Completable object of {@link java.lang.Void}. + */ + public static final class Void extends ValueBase { + /** + * Notify when this completable object callback. + */ + @AnyThread + @Override + protected void onComplete() { + synchronized (mStateLock) { + switch (mState) { + case CompletionState.NOT_COMPLETED: + mState = CompletionState.COMPLETED_WITH_VALUE; + break; + default: + throw new UnsupportedOperationException( + "onComplete() is not allowed on state=" + stateToString(mState)); + } + } + super.onComplete(); + } + + /** + * @throws RuntimeException when called while {@link #onError} happened. + * @throws UnsupportedOperationException when called while {@link #hasValue()} returns + * {@code false}. + */ + @AnyThread + public void getValue() { + synchronized (mStateLock) { + enforceGetValueLocked(); + } + } + } + + /** * Base class of completable object types. * * @param <T> type associated with this completable object. @@ -396,6 +444,13 @@ public final class Completable { } /** + * @return an instance of {@link Completable.Void}. + */ + public static Completable.Void createVoid() { + return new Completable.Void(); + } + + /** * Completable object of {@link java.lang.Boolean}. */ public static final class Boolean extends Values<java.lang.Boolean> { } @@ -444,7 +499,7 @@ public final class Completable { /** * Await the result by the {@link Completable.Values}. * - * @return the result once {@link ValueBase#onComplete()} + * @return the result once {@link ValueBase#onComplete()}. */ @AnyThread @Nullable @@ -456,7 +511,7 @@ public final class Completable { /** * 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) { @@ -465,6 +520,17 @@ public final class Completable { } /** + * Await the result by the {@link Completable.Void}. + * + * Check the result once {@link ValueBase#onComplete()} + */ + @AnyThread + public static void getResult(@NonNull Completable.Void value) { + value.await(); + value.getValue(); + } + + /** * Await the result by the {@link Completable.Int}, and log it if there is no result after * given timeout. * diff --git a/core/java/com/android/internal/inputmethod/IVoidResultCallback.aidl b/core/java/com/android/internal/inputmethod/IVoidResultCallback.aidl new file mode 100644 index 000000000000..0b25a2b886c9 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/IVoidResultCallback.aidl @@ -0,0 +1,24 @@ +/* + * 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.inputmethod; + +import com.android.internal.inputmethod.ThrowableHolder; + +oneway interface IVoidResultCallback { + void onResult(); + void onError(in ThrowableHolder exception); +}
\ No newline at end of file diff --git a/core/java/com/android/internal/inputmethod/ResultCallbacks.java b/core/java/com/android/internal/inputmethod/ResultCallbacks.java index 6ce851b59ccd..2a48c1f60aa9 100644 --- a/core/java/com/android/internal/inputmethod/ResultCallbacks.java +++ b/core/java/com/android/internal/inputmethod/ResultCallbacks.java @@ -352,4 +352,39 @@ public final class ResultCallbacks { } }; } + + /** + * Creates {@link IVoidResultCallback.Stub} that is to set {@link Completable.Void} when + * receiving the result. + * + * @param value {@link Completable.Void} to be set when receiving the result. + * @return {@link IVoidResultCallback.Stub} that can be passed as a binder IPC parameter. + */ + @AnyThread + public static IVoidResultCallback.Stub of(@NonNull Completable.Void value) { + final AtomicReference<WeakReference<Completable.Void>> atomicRef = + new AtomicReference<>(new WeakReference<>(value)); + + return new IVoidResultCallback.Stub() { + @BinderThread + @Override + public void onResult() { + final Completable.Void value = unwrap(atomicRef); + if (value == null) { + return; + } + value.onComplete(); + } + + @BinderThread + @Override + public void onError(ThrowableHolder throwableHolder) { + final Completable.Void value = unwrap(atomicRef); + if (value == null) { + return; + } + value.onError(throwableHolder); + } + }; + } } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 771a72c98a7a..1e2ce288974c 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -463,7 +463,7 @@ public class InteractionJankMonitor { } public String getName() { - return "Cuj<" + getNameOfCuj(mCujType) + ">"; + return "J<" + getNameOfCuj(mCujType) + ">"; } } } diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java index 9c3bb761b9f7..6609ebe49bf6 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 @@ import java.util.List; * 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 @@ public class AmbientDisplayPowerCalculator extends PowerCalculator { @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 000000000000..79b331da9c8a --- /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 93dff9f76df1..33aa19078c9f 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -873,7 +873,9 @@ public class BatteryStatsImpl extends BatteryStats { 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 9904d301ef72..e5d64a0e3c84 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -66,7 +66,8 @@ public class BatteryUsageStatsProvider { 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 f5690e0de38f..4c3b950ff715 100644 --- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java +++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java @@ -29,8 +29,8 @@ import android.util.SparseArray; 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 class BluetoothPowerCalculator extends PowerCalculator { 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 @@ public class BluetoothPowerCalculator extends PowerCalculator { return; } - mTotalPowerAndDuration.durationMs = 0; - mTotalPowerAndDuration.powerMah = 0; + final PowerAndDuration total = new PowerAndDuration(); SystemBatteryConsumer.Builder systemBatteryConsumerBuilder = builder.getOrCreateSystemBatteryConsumerBuilder( @@ -72,24 +66,25 @@ public class BluetoothPowerCalculator extends PowerCalculator { 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 @@ public class BluetoothPowerCalculator extends PowerCalculator { 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 @@ public class BluetoothPowerCalculator extends PowerCalculator { 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 @@ public class BluetoothPowerCalculator extends PowerCalculator { } } - @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 @@ public class BluetoothPowerCalculator extends PowerCalculator { 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 0365d9e9d600..6f8e9271d198 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 @@ import android.os.BatteryStats; * 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(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { + 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); + } - // 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; - } + @Override + protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, + 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 330feef8f117..6c29a91f081f 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(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { + 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); + } - // 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; - } + @Override + protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, + 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 44ad34417a4c..dcc8a15b2f50 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 @@ import java.util.List; 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); + } + + @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) { + 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); + } } /** - * Calculate the baseline power usage for the device when it is in suspend and idle. + * 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. */ - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - long batteryUptimeUs = batteryStats.computeBatteryUptime(rawUptimeUs, statsType); + 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))); - } - - if (totalPowerMah != 0) { - BatterySipper bs = new BatterySipper(BatterySipper.DrainType.IDLE, null, 0); - bs.usagePowerMah = totalPowerMah; - bs.usageTimeMs = batteryRealtimeUs / 1000; - bs.sumPower(); - sippers.add(bs); + + " power=" + formatCharge(idlePowerMah)); } + mDurationMs = batteryRealtimeUs / 1000; } } diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java index 2dd51b4459e7..f7fad2c5bbaa 100644 --- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java +++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java @@ -143,10 +143,6 @@ public abstract class KernelCpuUidTimeReader<T> { */ public void removeUid(int uid) { mLastTimes.delete(uid); - - if (mBpfTimesAvailable) { - mBpfReader.removeUidsInRange(uid, uid); - } } /** diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java index 10d9b6519992..df4605838b28 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 8f78b2a3a5ea..1b07aa0cf0b7 100644 --- a/core/java/com/android/internal/os/OWNERS +++ b/core/java/com/android/internal/os/OWNERS @@ -6,3 +6,5 @@ per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS 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 992c487a9a66..6ab8c90d21d9 100644 --- a/core/java/com/android/internal/os/PhonePowerCalculator.java +++ b/core/java/com/android/internal/os/PhonePowerCalculator.java @@ -30,20 +30,20 @@ import java.util.List; * 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 @@ public class PhonePowerCalculator extends PowerCalculator { @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 09fb75bc2d2d..25f6b4d16971 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 @@ public class ScreenPowerCalculator extends PowerCalculator { 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 @@ public class ScreenPowerCalculator extends PowerCalculator { @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); + 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 = 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 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) { + 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(p / (60 * 60 * 1000))); + + " power=" + formatCharge(binPowerMah)); } - power += p; - } - power /= (60 * 60 * 1000); // To hours - if (power != 0) { - final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.SCREEN, null, 0); - bs.usagePowerMah = power; - bs.usageTimeMs = screenOnTimeMs; - bs.sumPower(); - sippers.add(bs); + 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 b15dff67b619..55fc1bb607a9 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 @@ public class SystemServicePowerCalculator extends PowerCalculator { } @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 @@ public class SystemServicePowerCalculator extends PowerCalculator { } } + 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 000000000000..5910b61f0f6f --- /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 000000000000..5d6caf578475 --- /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/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index 254c2997fa65..1e9801f5ef30 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -91,18 +91,6 @@ public class LatencyTracker { */ public static final int ACTION_START_RECENTS_ANIMATION = 8; - private static final String[] NAMES = new String[]{ - "expand panel", - "toggle recents", - "fingerprint wake-and-unlock", - "check credential", - "check credential unlocked", - "turn on screen", - "rotate the screen", - "face wake-and-unlock", - "start recents-animation", - }; - private static final int[] STATSD_ACTION = new int[]{ FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL, FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS, @@ -185,6 +173,10 @@ public class LatencyTracker { } } + private String getTraceNameOfAcion(int action) { + return "L<" + getNameOfAction(action) + ">"; + } + public static boolean isEnabled(Context ctx) { return getInstance(ctx).isEnabled(); } @@ -202,7 +194,7 @@ public class LatencyTracker { if (!isEnabled()) { return; } - Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, NAMES[action], 0); + Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, getTraceNameOfAcion(action), 0); mStartRtc.put(action, SystemClock.elapsedRealtime()); } @@ -221,7 +213,7 @@ public class LatencyTracker { return; } mStartRtc.delete(action); - Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, NAMES[action], 0); + Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, getTraceNameOfAcion(action), 0); logAction(action, (int) (endRtc - startRtc)); } diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index 3a7e66ce76f0..e7b7bf4a5b52 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -79,6 +79,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { private static final int DO_CLOSE_CONNECTION = 150; private static final int DO_COMMIT_CONTENT = 160; private static final int DO_GET_SURROUNDING_TEXT = 41; + private static final int DO_SET_IME_TEMPORARILY_CONSUMES_INPUT = 170; @GuardedBy("mLock") @@ -266,6 +267,16 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { dispatchMessage(mH.obtainMessage(DO_COMMIT_CONTENT, flags, 0 /* unused */, args)); } + /** + * Dispatches the request for setting ime temporarily consumes input. + * + * <p>See {@link InputConnection#setImeTemporarilyConsumesInput(boolean)}. + */ + public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) { + dispatchMessage(obtainMessageB(DO_SET_IME_TEMPORARILY_CONSUMES_INPUT, + imeTemporarilyConsumesInput)); + } + void dispatchMessage(Message msg) { // If we are calling this from the main thread, then we can call // right through. Otherwise, we need to send the message to the @@ -811,6 +822,22 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } return; } + case DO_SET_IME_TEMPORARILY_CONSUMES_INPUT: { + Trace.traceBegin(Trace.TRACE_TAG_INPUT, + "InputConnection#setImeTemporarilyConsumesInput"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, + "setImeTemporarilyConsumesInput on inactive InputConnection"); + return; + } + ic.setImeTemporarilyConsumesInput(msg.arg1 == 1); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); + } + return; + } } Log.w(TAG, "Unhandled message code: " + msg.what); } @@ -837,4 +864,8 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { args.arg2 = arg2; return mH.obtainMessage(what, 0, 0, args); } + + Message obtainMessageB(int what, boolean arg1) { + return mH.obtainMessage(what, arg1 ? 1 : 0, 0); + } } diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index 53cbf961fff4..586404c53f18 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -85,4 +85,6 @@ import com.android.internal.inputmethod.ISurroundingTextResultCallback; void getSurroundingText(int beforeLength, int afterLength, int flags, ISurroundingTextResultCallback callback); + + void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput); } diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index af9c0124078a..84c92ca83f36 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -524,6 +524,19 @@ public class InputConnectionWrapper implements InputConnection { value, TAG, "commitContent()", mCancellationGroup, MAX_WAIT_TIME_MILLIS) != 0; } + /** + * See {@link InputConnection#setImeTemporarilyConsumesInput(boolean)}. + */ + @AnyThread + public boolean setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) { + try { + mIInputContext.setImeTemporarilyConsumesInput(imeTemporarilyConsumesInput); + return true; + } catch (RemoteException e) { + return false; + } + } + @AnyThread private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) { return (mMissingMethods & methodFlag) == methodFlag; diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java index 767ad42efbf3..4ccf9ce91f27 100644 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ b/core/java/com/android/internal/widget/EditableInputConnection.java @@ -245,6 +245,15 @@ public class EditableInputConnection extends BaseInputConnection } @Override + public boolean setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) { + if (mTextView == null) { + return super.setImeTemporarilyConsumesInput(imeTemporarilyConsumesInput); + } + mTextView.setImeTemporarilyConsumesInput(imeTemporarilyConsumesInput); + return true; + } + + @Override public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); CharSequence editableText = mTextView.getText(); diff --git a/core/jni/Android.bp b/core/jni/Android.bp index c05e7a2166d9..94ef64f43ce2 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -308,6 +308,11 @@ cc_library_shared { linux_glibc: { srcs: [ "android_content_res_ApkAssets.cpp", + "android_database_CursorWindow.cpp", + "android_database_SQLiteCommon.cpp", + "android_database_SQLiteConnection.cpp", + "android_database_SQLiteGlobal.cpp", + "android_database_SQLiteDebug.cpp", "android_hardware_input_InputApplicationHandle.cpp", "android_os_MessageQueue.cpp", "android_os_Parcel.cpp", @@ -331,6 +336,7 @@ cc_library_shared { static_libs: [ "libinput", "libbinderthreadstateutils", + "libsqlite", ], shared_libs: [ // libbinder needs to be shared since it has global state @@ -341,12 +347,6 @@ cc_library_shared { }, }, - product_variables: { - experimental_mte: { - cflags: ["-DANDROID_EXPERIMENTAL_MTE"], - }, - }, - // Workaround Clang LTO crash. lto: { never: true, diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index 4e50f87c086b..3e513df98f14 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -41,6 +41,10 @@ extern int register_android_content_AssetManager(JNIEnv* env); extern int register_android_content_StringBlock(JNIEnv* env); extern int register_android_content_XmlBlock(JNIEnv* env); extern int register_android_content_res_ApkAssets(JNIEnv* env); +extern int register_android_database_CursorWindow(JNIEnv* env); +extern int register_android_database_SQLiteConnection(JNIEnv* env); +extern int register_android_database_SQLiteGlobal(JNIEnv* env); +extern int register_android_database_SQLiteDebug(JNIEnv* env); extern int register_android_os_FileObserver(JNIEnv* env); extern int register_android_os_MessageQueue(JNIEnv* env); extern int register_android_os_SystemClock(JNIEnv* env); @@ -65,6 +69,11 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { #ifdef __linux__ {"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)}, {"android.content.res.AssetManager", REG_JNI(register_android_content_AssetManager)}, + {"android.database.CursorWindow", REG_JNI(register_android_database_CursorWindow)}, + {"android.database.sqlite.SQLiteConnection", + REG_JNI(register_android_database_SQLiteConnection)}, + {"android.database.sqlite.SQLiteGlobal", REG_JNI(register_android_database_SQLiteGlobal)}, + {"android.database.sqlite.SQLiteDebug", REG_JNI(register_android_database_SQLiteDebug)}, #endif {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)}, {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)}, diff --git a/core/proto/android/app/OWNERS b/core/proto/android/app/OWNERS new file mode 100644 index 000000000000..296abd18aadc --- /dev/null +++ b/core/proto/android/app/OWNERS @@ -0,0 +1 @@ +per-file location_time_zone_manager.proto = nfuller@google.com, mingaleev@google.com diff --git a/core/proto/android/app/location_time_zone_manager.proto b/core/proto/android/app/location_time_zone_manager.proto new file mode 100644 index 000000000000..f44d5495f132 --- /dev/null +++ b/core/proto/android/app/location_time_zone_manager.proto @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package android.app.time; + +import "frameworks/base/core/proto/android/privacy.proto"; + +option java_multiple_files = true; +option java_outer_classname = "LocationTimeZoneManagerProto"; + +// Represents the state of the LocationTimeZoneManagerService for use in tests. +message LocationTimeZoneManagerServiceStateProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional GeolocationTimeZoneSuggestionProto last_suggestion = 1; + repeated TimeZoneProviderStateProto primary_provider_states = 2; + repeated TimeZoneProviderStateProto secondary_provider_states = 3; +} + +// Represents a GeolocationTimeZoneSuggestion that can be / has been passed to the time zone +// detector. +message GeolocationTimeZoneSuggestionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + repeated string zone_ids = 1; + repeated string debug_info = 2; +} + +// The state tracked for a LocationTimeZoneProvider. +message TimeZoneProviderStateProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional TimeZoneProviderStateEnum state = 1; +} + +// The state enum for LocationTimeZoneProviders. +enum TimeZoneProviderStateEnum { + TIME_ZONE_PROVIDER_STATE_UNKNOWN = 0; + TIME_ZONE_PROVIDER_STATE_INITIALIZING = 1; + TIME_ZONE_PROVIDER_STATE_CERTAIN = 2; + TIME_ZONE_PROVIDER_STATE_UNCERTAIN = 3; + TIME_ZONE_PROVIDER_STATE_DISABLED = 4; + TIME_ZONE_PROVIDER_STATE_PERM_FAILED = 5; + TIME_ZONE_PROVIDER_STATE_DESTROYED = 6; +} diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto index 07e938ddfc5d..401e0038cada 100644 --- a/core/proto/android/hardware/sensorprivacy.proto +++ b/core/proto/android/hardware/sensorprivacy.proto @@ -25,11 +25,29 @@ import "frameworks/base/core/proto/android/privacy.proto"; message SensorPrivacyServiceDumpProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; + // DEPRECATED // Is global sensor privacy enabled optional bool is_enabled = 1; + // DEPRECATED // Per sensor privacy enabled repeated SensorPrivacyIndividualEnabledSensorProto individual_enabled_sensor = 2; + + // Per user settings for sensor privacy + repeated SensorPrivacyUserProto user = 3; +} + +message SensorPrivacyUserProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + // User id + optional int32 user_id = 1; + + // Is global sensor privacy enabled + optional bool is_enabled = 2; + + // Per sensor privacy enabled + repeated SensorPrivacyIndividualEnabledSensorProto individual_enabled_sensor = 3; } message SensorPrivacyIndividualEnabledSensorProto { diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 8de30f8547a7..944edb0ff750 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -521,6 +521,11 @@ message IncidentProto { (section).args = "power_stats --proto model" ]; + optional com.android.server.powerstats.PowerStatsServiceResidencyProto powerstats_residency = 3056 [ + (section).type = SECTION_DUMPSYS, + (section).args = "power_stats --proto residency" + ]; + // Dumps in text format (on userdebug and eng builds only): 4000 ~ 4999 optional android.util.TextDumpProto textdump_wifi = 4000 [ (section).type = SECTION_TEXT_DUMPSYS, diff --git a/core/proto/android/server/powerstatsservice.proto b/core/proto/android/server/powerstatsservice.proto index 9a7ed7cbe98f..30c427433639 100644 --- a/core/proto/android/server/powerstatsservice.proto +++ b/core/proto/android/server/powerstatsservice.proto @@ -41,6 +41,16 @@ message IncidentReportModelProto { } /** + * IncidentReportResidencyProto is used only in the parsing tool located + * in frameworks/base/tools which is used to parse this data out of + * incident reports. + */ +message IncidentReportResidencyProto { + /** Section number matches that in incident.proto */ + optional PowerStatsServiceResidencyProto incident_report = 3056; +} + +/** * EnergyConsumer (model) data is exposed by the PowerStats HAL. This data * represents modeled energy consumption estimates and is provided per * subsystem. The default subsystems are defined in EnergyConsumerId.aidl. @@ -63,6 +73,99 @@ message PowerStatsServiceMeterProto { } /** + * A PowerEntity is defined as a platform subsystem, peripheral, or power domain + * that impacts the total device power consumption. PowerEntityInfo is + * information related to each power entity. Each PowerEntity may reside in one + * of multiple states. It may also transition from one state to another. + * StateResidency is defined as an accumulation of time that a PowerEntity + * resided in each of its possible states, the number of times that each state + * was entered, and a timestamp corresponding to the last time that state was + * entered. + */ +message PowerStatsServiceResidencyProto { + repeated PowerEntityInfoProto power_entity_info = 1; + repeated StateResidencyResultProto state_residency_result = 2; +} + +/** + * Information about the possible states for a particular PowerEntity. + */ +message StateInfoProto { + /** + * Unique (for a given PowerEntityInfo) ID of this StateInfo + */ + optional int32 state_id = 1; + /** + * Unique (for a given PowerEntityInfo) name of the state. Vendor/device specific. + * Opaque to framework + */ + optional string state_name = 2; +} + +/** + * A PowerEntity is defined as a platform subsystem, peripheral, or power domain + * that impacts the total device power consumption. PowerEntityInfo is + * information about a PowerEntity. It includes an array of information about + * each possible state of the PowerEntity. + */ +message PowerEntityInfoProto { + /** + * Unique ID of this PowerEntityInfo + */ + optional int32 power_entity_id = 1; + /** + * Unique name of the PowerEntity. Vendor/device specific. Opaque to framework + */ + optional string power_entity_name = 2; + /** + * List of states that the PowerEntity may reside in + */ + repeated StateInfoProto states = 3; +} + +/** + * StateResidency is defined as an accumulation of time that a PowerEntity + * resided in each of its possible states, the number of times that each state + * was entered, and a timestamp corresponding to the last time that state was + * entered. Data is accumulated starting at device boot. + */ +message StateResidencyProto { + /** + * ID of the state associated with this residency + */ + optional int32 state_id = 1; + /** + * Total time in milliseconds that the corresponding PowerEntity resided + * in this state since boot + */ + optional int64 total_time_in_state_ms = 2; + /** + * Total number of times that the state was entered since boot + */ + optional int64 total_state_entry_count = 3; + /** + * Last time this state was entered. Time in milliseconds since boot + */ + optional int64 last_entry_timestamp_ms = 4; +} + +/** + * A StateResidencyResult is an array of StateResidencies for a particular + * PowerEntity. The StateResidencyResult can be matched to its corresponding + * PowerEntityInfo through the power_entity_id field. + */ +message StateResidencyResultProto { + /** + * ID of the PowerEntity associated with this result + */ + optional int32 power_entity_id = 1; + /** + * Residency for each state in the PowerEntity's state space + */ + repeated StateResidencyProto state_residency_data = 2; +} + +/** * Energy consumer ID: * A list of default subsystems for which energy consumption estimates * may be provided (hardware dependent). diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8682fea1f8dc..ce3ed9dc8ba6 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). @@ -5317,6 +5325,12 @@ <permission android:name="android.permission.BIND_IMPRESSION_ATTESTATION_SERVICE" android:protectionLevel="signature" /> + <!-- @hide @TestApi Allows an application to enable/disable toast rate limiting. + <p>Not for use by third-party applications. + --> + <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" + android:protectionLevel="signature" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/assets/images/progress_font.png b/core/res/assets/images/progress_font.png Binary files differnew file mode 100644 index 000000000000..78c3ed9cd699 --- /dev/null +++ b/core/res/assets/images/progress_font.png diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index 000638475a10..41be36bc2b04 100644 --- a/core/res/res/layout/notification_template_material_base.xml +++ b/core/res/res/layout/notification_template_material_base.xml @@ -45,6 +45,45 @@ android:padding="@dimen/notification_icon_circle_padding" /> + <ImageView + android:id="@+id/right_icon" + android:layout_width="@dimen/notification_right_icon_size" + android:layout_height="@dimen/notification_right_icon_size" + android:layout_gravity="center_vertical|end" + android:layout_marginTop="@dimen/notification_right_icon_headerless_margin" + android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin" + android:layout_marginEnd="@dimen/notification_header_expand_icon_size" + android:background="@drawable/notification_large_icon_outline" + android:importantForAccessibility="no" + android:scaleType="centerCrop" + /> + + <FrameLayout + android:id="@+id/alternate_expand_target" + android:layout_width="@dimen/notification_content_margin_start" + android:layout_height="match_parent" + android:layout_gravity="start" + android:importantForAccessibility="no" + /> + + <FrameLayout + android:id="@+id/expand_button_touch_container" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="end"> + + <com.android.internal.widget.NotificationExpandButton + android:id="@+id/expand_button" + android:layout_width="@dimen/notification_header_expand_icon_size" + android:layout_height="@dimen/notification_header_expand_icon_size" + android:layout_gravity="center_vertical|end" + android:contentDescription="@string/expand_button_content_description_collapsed" + android:paddingTop="@dimen/notification_expand_button_padding_top" + android:scaleType="center" + /> + + </FrameLayout> + <LinearLayout android:id="@+id/notification_headerless_view_column" android:layout_width="match_parent" @@ -64,6 +103,7 @@ variant is 56dp and the 2- and 3-line variants are both 76dp. --> <FrameLayout + android:id="@+id/notification_headerless_margin_extra_top" android:layout_width="match_parent" android:layout_height="@dimen/notification_headerless_margin_extra" android:layout_weight="1" @@ -135,6 +175,7 @@ variant is 56dp and the 2- and 3-line variants are both 76dp. --> <FrameLayout + android:id="@+id/notification_headerless_margin_extra_bottom" android:layout_width="match_parent" android:layout_height="@dimen/notification_headerless_margin_extra" android:layout_weight="1" @@ -142,43 +183,4 @@ </LinearLayout> - <ImageView - android:id="@+id/right_icon" - android:layout_width="@dimen/notification_right_icon_size" - android:layout_height="@dimen/notification_right_icon_size" - android:layout_gravity="center_vertical|end" - android:layout_marginTop="@dimen/notification_right_icon_headerless_margin" - android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin" - android:layout_marginEnd="@dimen/notification_header_expand_icon_size" - android:background="@drawable/notification_large_icon_outline" - android:importantForAccessibility="no" - android:scaleType="centerCrop" - /> - - <FrameLayout - android:id="@+id/alternate_expand_target" - android:layout_width="@dimen/notification_content_margin_start" - android:layout_height="match_parent" - android:layout_gravity="start" - android:importantForAccessibility="no" - /> - - <FrameLayout - android:id="@+id/expand_button_touch_container" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_gravity="end"> - - <com.android.internal.widget.NotificationExpandButton - android:id="@+id/expand_button" - android:layout_width="@dimen/notification_header_expand_icon_size" - android:layout_height="@dimen/notification_header_expand_icon_size" - android:layout_gravity="center_vertical|end" - android:contentDescription="@string/expand_button_content_description_collapsed" - android:paddingTop="@dimen/notification_expand_button_padding_top" - android:scaleType="center" - /> - - </FrameLayout> - </com.android.internal.widget.NotificationMaxHeightFrameLayout> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index 0721f8eeea35..3adb318dd8b0 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -100,7 +100,7 @@ <string name="peerTtyModeHco" msgid="5626377160840915617">"নেটৱৰ্ক পীয়েৰে TTY ম\'ড HCOলৈ সলনি কৰিবলৈ অনুৰোধ কৰিছে"</string> <string name="peerTtyModeVco" msgid="572208600818270944">"নেটৱৰ্ক পীয়েৰে TTY ম\'ড VCO লৈ সলনি কৰিবলৈ অনুৰোধ কৰিছে"</string> <string name="peerTtyModeOff" msgid="2420380956369226583">"নেটৱৰ্ক পীয়েৰে TTY ম\'ড OFFলৈ সলনি কৰিবলৈ অনুৰোধ কৰিছে"</string> - <string name="serviceClassVoice" msgid="2065556932043454987">"কণ্ঠস্বৰ"</string> + <string name="serviceClassVoice" msgid="2065556932043454987">"Voice"</string> <string name="serviceClassData" msgid="4148080018967300248">"ডেটা"</string> <string name="serviceClassFAX" msgid="2561653371698904118">"ফেক্স"</string> <string name="serviceClassSMS" msgid="1547664561704509004">"এছএমএছ"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 0fea93646f9c..8dced4b23a6f 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -2009,14 +2009,10 @@ <string name="notification_appops_microphone_active" msgid="581333393214739332">"মাইক্রোফোন"</string> <string name="notification_appops_overlay_active" msgid="5571732753262836481">"স্ক্রিনে অন্যান্য অ্যাপের উপরে দেখানো হচ্ছে"</string> <string name="notification_feedback_indicator" msgid="663476517711323016">"মতামত জানান"</string> - <!-- no translation found for notification_feedback_indicator_alerted (6552871804121942099) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_silenced (3799442124723177262) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_promoted (9030204303764698640) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_demoted (8880309924296450875) --> - <skip /> + <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"এই বিজ্ঞপ্তির গুরুত্ব বাড়িয়ে ডিফল্ট হিসেবে সেট করা হয়েছে। মতামত জানাতে ট্যাপ করুন।"</string> + <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"এই বিজ্ঞপ্তির গুরুত্ব কমিয়ে মিউট হিসেবে সেট করা হয়েছে। মতামত জানাতে ট্যাপ করুন।"</string> + <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"এই বিজ্ঞপ্তির গুরুত্ব বাড়ানো হয়েছে। মতামত জানাতে ট্যাপ করুন।"</string> + <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"এই বিজ্ঞপ্তির গুরুত্ব কমানো হয়েছে। মতামত জানাতে ট্যাপ করুন।"</string> <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"রুটিন মোডের তথ্য সংক্রান্ত বিজ্ঞপ্তি"</string> <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"সাধারণত যখন চার্জ দেন, তার আগে চার্জ শেষ হয়ে যেতে পারে"</string> <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"ডিভাইস বেশিক্ষণ চালু রাখতে ব্যাটারি সেভার চালু করা হয়েছে"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index f23cb1a0e1fa..366c50657257 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -2009,14 +2009,10 @@ <string name="notification_appops_microphone_active" msgid="581333393214739332">"Micrófono"</string> <string name="notification_appops_overlay_active" msgid="5571732753262836481">"se muestra sobre otras aplicaciones que haya en la pantalla"</string> <string name="notification_feedback_indicator" msgid="663476517711323016">"Enviar comentarios"</string> - <!-- no translation found for notification_feedback_indicator_alerted (6552871804121942099) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_silenced (3799442124723177262) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_promoted (9030204303764698640) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_demoted (8880309924296450875) --> - <skip /> + <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"La importancia de esta notificación ha aumentado a Predeterminado. Toca para enviar comentarios."</string> + <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"La importancia de esta notificación ha disminuido a Silencio. Toca para enviar comentarios."</string> + <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Esta notificación aparecerá en una posición más alta. Toca para enviar comentarios."</string> + <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Esta notificación aparecerá en una posición más baja. Toca para enviar comentarios."</string> <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificación sobre el modo rutina"</string> <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Quizás se agote la batería antes de lo habitual"</string> <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Se ha activado el modo Ahorro de batería para aumentar la duración de la batería"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 9bc37c111375..4ba95bb81c27 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -352,15 +352,15 @@ <string name="permlab_receiveMms" msgid="4000650116674380275">"jaso testu-mezuak (MMSak)"</string> <string name="permdesc_receiveMms" msgid="958102423732219710">"MMS mezuak jasotzeko eta prozesatzeko baimena ematen die aplikazioei. Horrela, aplikazioak gailura bidalitako mezuak kontrola eta ezaba ditzake zuri erakutsi gabe."</string> <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"desbideratu sare mugikor bidezko igorpen-mezuak"</string> - <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Sare mugikor bidezko igorpen-modulura lotzeko baimena ematen dio aplikazioari, sare mugikor bidezko igorpen-mezuak jaso ahala desbideratu ahal izateko. Sare mugikor bidezko igorpen-alertak kokapen batzuetan entregatzen dira larrialdi-egoeren berri emateko. Sare mugikor bidezko larrialdi-igorpenak jasotzean, aplikazio gaiztoek gailuaren errendimenduari edota funtzionamenduari eragin diezaiokete."</string> + <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Sare mugikor bidezko igorpen-modulura lotzeko baimena ematen dio aplikazioari, sare mugikor bidezko igorpen-mezuak jaso ahala desbideratu ahal izateko. Sare mugikor bidezko igorpen-alertak kokapen batzuetan entregatzen dira larrialdi-egoeren berri emateko. Sare mugikor bidezko larrialdi-igorpenak jasotzean, asmo txarreko aplikazioek gailuaren errendimenduari edota funtzionamenduari eragin diezaiokete."</string> <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Kudeatu abian dauden deiak"</string> <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Gailuak jasotzen dituen deiei buruzko xehetasunak ikusteko eta dei horiek kontrolatzeko baimena ematen die aplikazioei."</string> <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"irakurri sare mugikor bidezko igorpen-mezuak"</string> - <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Gailuak jasotako sare mugikor bidezko igorpenen mezuak irakurtzeko baimena ematen die aplikazioei. Sare mugikor bidezko igorpen-alertak kokapen batzuetan ematen dira larrialdi-egoeren berri emateko. Aplikazio gaiztoek gailuaren errendimendua edo funtzionamendua oztopa dezakete larrialdi-igorpen horietako bat jasotzen denean."</string> + <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Gailuak jasotako sare mugikor bidezko igorpenen mezuak irakurtzeko baimena ematen die aplikazioei. Sare mugikor bidezko igorpen-alertak kokapen batzuetan ematen dira larrialdi-egoeren berri emateko. Asmo txarreko aplikazioek gailuaren errendimendua edo funtzionamendua oztopa dezakete larrialdi-igorpen horietako bat jasotzen denean."</string> <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"irakurri harpidetutako jarioak"</string> <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"Une horretan sinkronizatutako jarioei buruzko xehetasunak lortzeko baimena ematen die aplikazioei."</string> <string name="permlab_sendSms" msgid="7757368721742014252">"bidali eta ikusi SMS mezuak"</string> - <string name="permdesc_sendSms" msgid="6757089798435130769">"SMS mezuak bidaltzeko baimena ematen die aplikazioei. Horrela, ustekabeko gastuak eragin daitezke. Aplikazio gaiztoek erabil dezakete zuk berretsi gabeko mezuak bidalita gastuak eragiteko."</string> + <string name="permdesc_sendSms" msgid="6757089798435130769">"SMS mezuak bidaltzeko baimena ematen die aplikazioei. Horrela, ustekabeko gastuak eragin daitezke. Asmo txarreko aplikazioek erabil dezakete zuk berretsi gabeko mezuak bidalita gastuak eragiteko."</string> <string name="permlab_readSms" msgid="5164176626258800297">"irakurri testu-mezuak (SMSak edo MMSak)"</string> <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"Aplikazioak tabletan gordetako SMS mezu (testu-mezu) guztiak irakur ditzake."</string> <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"Aplikazioek Android TV gailuan gordetako SMS (testu) mezu guztiak irakur ditzakete."</string> @@ -392,7 +392,7 @@ <string name="permlab_getPackageSize" msgid="375391550792886641">"neurtu aplikazioen biltegiratzeko tokia"</string> <string name="permdesc_getPackageSize" msgid="742743530909966782">"Bere kodea, datuak eta cache-tamainak eskuratzeko baimena ematen die aplikazioei."</string> <string name="permlab_writeSettings" msgid="8057285063719277394">"aldatu sistemaren ezarpenak"</string> - <string name="permdesc_writeSettings" msgid="8293047411196067188">"Sistemaren ezarpenen datuak aldatzeko baimena ematen die aplikazioei. Aplikazio gaiztoek sistemaren konfigurazioa hondatzeko erabil dezakete."</string> + <string name="permdesc_writeSettings" msgid="8293047411196067188">"Sistemaren ezarpenen datuak aldatzeko baimena ematen die aplikazioei. Asmo txarreko aplikazioek sistemaren konfigurazioa hondatzeko erabil dezakete."</string> <string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"exekutatu abiaraztean"</string> <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"Sistema berrabiarazi bezain laster abiarazteko baimena ematen die aplikazioei. Horrela, agian denbora gehiago beharko du tabletak abiarazteko, eta tabletaren funtzionamendu orokorra mantso daiteke, baimen hori duten aplikazioak beti abian egongo baitira."</string> <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"Sistema abiarazi bezain laster beren burua abiarazteko baimena ematen die aplikazioei. Baliteke denbora gehiago behar izatea Android TV gailua abiarazteko eta aplikazioek gailua orokorrean mantsoago ibilarazteko baimena izatea, beti abian izango baita."</string> @@ -402,9 +402,9 @@ <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Igorpen iraunkorrak egiteko baimena ematen die aplikazioei. Igorpena amaitu ondoren ere igortzen jarraitzen dute igorpen iraunkorrek. Gehiegi erabiliz gero, Android TV gailua motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string> <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Igorpen iraunkorrak emateko baimena ematen die; horiek igorpena amaitu ondoren mantentzen dira. Gehiegi erabiliz gero, telefonoa motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string> <string name="permlab_readContacts" msgid="8776395111787429099">"irakurri kontaktuak"</string> - <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Tabletan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten tabletako kontuak ere atzitu ahalko dituzte aplikazioek. Horrek barnean hartuko ditu instalatutako aplikazioek sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzakete aplikazioek, eta baliteke aplikazio gaiztoek zuk jakin gabe partekatzea datu horiek."</string> - <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Android TV gailuan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten Android TV gailuko kontuak ere atzitu ahalko dituzte aplikazioek. Horrek barnean hartuko ditu instalatutako aplikazioek sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzakete aplikazioek, eta baliteke aplikazio gaiztoek zuk jakin gabe partekatzea datu horiek."</string> - <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Telefonoan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten telefonoko kontuak ere atzitu ahalko dituzte aplikazioek. Horrek barnean hartuko ditu instalatutako aplikazioek sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzakete aplikazioek, eta baliteke aplikazio gaiztoek zuk jakin gabe partekatzea datu horiek."</string> + <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Tabletan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten tabletako kontuak ere atzitu ahalko dituzte aplikazioek. Horrek barnean hartuko ditu instalatutako aplikazioek sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzakete aplikazioek, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string> + <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Android TV gailuan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten Android TV gailuko kontuak ere atzitu ahalko dituzte aplikazioek. Horrek barnean hartuko ditu instalatutako aplikazioek sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzakete aplikazioek, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string> + <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Telefonoan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten telefonoko kontuak ere atzitu ahalko dituzte aplikazioek. Horrek barnean hartuko ditu instalatutako aplikazioek sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzakete aplikazioek, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string> <string name="permlab_writeContacts" msgid="8919430536404830430">"aldatu kontaktuak"</string> <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Tabletan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen dio aplikazioari. Baimen horrekin, aplikazioek kontaktuen datuak ezaba ditzakete."</string> <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Android TV gailuan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen dio aplikazioari. Baimen horrekin, aplikazioek kontaktuen datuak ezaba ditzakete."</string> @@ -412,9 +412,9 @@ <string name="permlab_readCallLog" msgid="1739990210293505948">"irakurri deien erregistroa"</string> <string name="permdesc_readCallLog" msgid="8964770895425873433">"Aplikazioak deien historia irakur dezake."</string> <string name="permlab_writeCallLog" msgid="670292975137658895">"idatzi deien erregistroan"</string> - <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Tabletaren deien erregistroa aldatzeko baimena ematen die aplikazioei, sarrerako eta irteerako deiei buruzko datuak barne. Aplikazio gaiztoek deien erregistroa ezabatzeko edo aldatzeko erabil dezakete."</string> - <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Android TV gailuko deien erregistroa aldatzeko baimena ematen die aplikazioei, jasotako eta egindako deiei buruzko datuak barne. Baliteke aplikazio gaiztoek deien erregistroa ezabatzea edo aldatzea."</string> - <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Telefonoaren deien erregistroa aldatzeko baimena ematen die aplikazioei, sarrerako eta irteerako deiei buruzko datuak barne. Aplikazio gaiztoek deien erregistroa ezabatzeko edo aldatzeko erabil dezakete."</string> + <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Tabletaren deien erregistroa aldatzeko baimena ematen die aplikazioei, sarrerako eta irteerako deiei buruzko datuak barne. Asmo txarreko aplikazioek deien erregistroa ezabatzeko edo aldatzeko erabil dezakete."</string> + <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Android TV gailuko deien erregistroa aldatzeko baimena ematen die aplikazioei, jasotako eta egindako deiei buruzko datuak barne. Baliteke asmo txarreko aplikazioek deien erregistroa ezabatzea edo aldatzea."</string> + <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Telefonoaren deien erregistroa aldatzeko baimena ematen die aplikazioei, sarrerako eta irteerako deiei buruzko datuak barne. Asmo txarreko aplikazioek deien erregistroa ezabatzeko edo aldatzeko erabil dezakete."</string> <string name="permlab_bodySensors" msgid="3411035315357380862">"Atzitu gorputzaren sentsoreak (adibidez, bihotz-maiztasunarenak)"</string> <string name="permdesc_bodySensors" product="default" msgid="2365357960407973997">"Zure egoera fisikoa kontrolatzen duten sentsoreetako datuak (adibidez, bihotz-maiztasuna) atzitzeko baimena ematen die aplikazioei."</string> <string name="permlab_readCalendar" msgid="6408654259475396200">"irakurri egutegiko gertaerak eta xehetasunak"</string> @@ -455,7 +455,7 @@ <string name="permdesc_vibrate" msgid="8733343234582083721">"Bibragailua kontrolatzeko aukera ematen die aplikazioei."</string> <string name="permdesc_vibrator_state" msgid="7050024956594170724">"Dardara-egoera atzitzeko baimena ematen dio aplikazioari."</string> <string name="permlab_callPhone" msgid="1798582257194643320">"deitu zuzenean telefono-zenbakietara"</string> - <string name="permdesc_callPhone" msgid="5439809516131609109">"Telefono-zenbakietara zuk esku hartu gabe deitzeko baimena ematen die aplikazioei. Horrela, ustekabeko gastuak edo deiak eragin daitezke. Aplikazio gaiztoek erabil dezakete zuk berretsi gabeko deiak eginda gastuak eragiteko."</string> + <string name="permdesc_callPhone" msgid="5439809516131609109">"Telefono-zenbakietara zuk esku hartu gabe deitzeko baimena ematen die aplikazioei. Horrela, ustekabeko gastuak edo deiak eragin daitezke. Asmo txarreko aplikazioek erabil dezakete zuk berretsi gabeko deiak eginda gastuak eragiteko."</string> <string name="permlab_accessImsCallService" msgid="442192920714863782">"atzitu IMS dei-zerbitzua"</string> <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Zuk ezer egin beharrik gabe deiak egiteko IMS zerbitzua erabiltzeko baimena ematen die aplikazioei."</string> <string name="permlab_readPhoneState" msgid="8138526903259297969">"irakurri telefonoaren egoera eta identitatea"</string> @@ -972,7 +972,7 @@ <string name="permlab_addVoicemail" msgid="4770245808840814471">"gehitu erantzungailua"</string> <string name="permdesc_addVoicemail" msgid="5470312139820074324">"Erantzungailuko sarrera-ontzian mezuak gehitzeko baimena ematen die aplikazioei."</string> <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"aldatu arakatzailearen geokokapenaren baimenak"</string> - <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Arakatzailearen geokokapenaren baimenak aldatzeko baimena ematen die aplikazioei. Aplikazio gaiztoek hori erabil dezakete kokapenari buruzko informazioa haiek hautatutako webguneetara bidaltzeko."</string> + <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Arakatzailearen geokokapenaren baimenak aldatzeko baimena ematen die aplikazioei. Asmo txarreko aplikazioek hori erabil dezakete kokapenari buruzko informazioa haiek hautatutako webguneetara bidaltzeko."</string> <string name="save_password_message" msgid="2146409467245462965">"Arakatzaileak pasahitza gogoratzea nahi duzu?"</string> <string name="save_password_notnow" msgid="2878327088951240061">"Ez une honetan"</string> <string name="save_password_remember" msgid="6490888932657708341">"Gogoratu"</string> @@ -2009,10 +2009,10 @@ <string name="notification_appops_microphone_active" msgid="581333393214739332">"Mikrofonoa"</string> <string name="notification_appops_overlay_active" msgid="5571732753262836481">"pantailako beste aplikazioen gainean bistaratzen"</string> <string name="notification_feedback_indicator" msgid="663476517711323016">"Eman iritzia"</string> - <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Lehenetsi gisa ezarri da jakinarazpena. Sakatu hau iritzia emateko."</string> - <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Isilarazi da jakinarazpena. Sakatu hau iritzia emateko."</string> - <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Mailaz igo da jakinarazpena. Sakatu hau iritzia emateko."</string> - <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Mailaz jaitsi da jakinarazpena. Sakatu hau iritzia emateko."</string> + <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Lehenetsi gisa ezarri da jakinarazpena. Sakatu hau oharrak bidaltzeko."</string> + <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Isilarazi da jakinarazpena. Sakatu hau oharrak bidaltzeko."</string> + <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Mailaz igo da jakinarazpena. Sakatu hau oharrak bidaltzeko."</string> + <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Mailaz jaitsi da jakinarazpena. Sakatu hau oharrak bidaltzeko."</string> <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Ohitura moduaren informazio-jakinarazpena"</string> <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Baliteke bateria ohi baino lehenago agortzea"</string> <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Bateria-aurrezlea aktibatuta dago bateriaren iraupena luzatzeko"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index a3c1ec44773c..016e77b5970f 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -100,7 +100,7 @@ <string name="peerTtyModeHco" msgid="5626377160840915617">"પીઅરે TTY મોડ HCO ની વિનંતી કરી"</string> <string name="peerTtyModeVco" msgid="572208600818270944">"પીઅરે TTY મોડ VCO ની વિનંતી કરી"</string> <string name="peerTtyModeOff" msgid="2420380956369226583">"પીઅરે TTY મોડ બંધ કરવાની વિનંતી કરી"</string> - <string name="serviceClassVoice" msgid="2065556932043454987">"અવાજ"</string> + <string name="serviceClassVoice" msgid="2065556932043454987">"Voice"</string> <string name="serviceClassData" msgid="4148080018967300248">"ડેટા"</string> <string name="serviceClassFAX" msgid="2561653371698904118">"ફેક્સ"</string> <string name="serviceClassSMS" msgid="1547664561704509004">"SMS"</string> @@ -1319,7 +1319,7 @@ <string name="usb_power_notification_message" msgid="7284765627437897702">"કનેક્ટ કરેલ ઉપકરણ ચાર્જ થઈ રહ્યું છે. વધુ વિકલ્પો માટે ટૅપ કરો."</string> <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"એનાલોગ ઑડિઓ ઍક્સેસરી મળી"</string> <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"જોડેલ ઉપકરણ આ ફોન સાથે સુસંગત નથી. વધુ જાણવા માટે ટૅપ કરો."</string> - <string name="adb_active_notification_title" msgid="408390247354560331">"USB ડીબગિંગ કનેક્ટ થયું."</string> + <string name="adb_active_notification_title" msgid="408390247354560331">"USB ડિબગીંગ કનેક્ટ થયું."</string> <string name="adb_active_notification_message" msgid="5617264033476778211">"USB ડિબગીંગ બંધ કરવા માટે ટૅપ કરો"</string> <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"USB ડિબગીંગને અક્ષમ કરવા માટે પસંદ કરો."</string> <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"વાયરલેસ ડિબગીંગ કનેક્ટ કરો"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 7fb27f10dd58..a8afbbecf0c2 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -100,7 +100,7 @@ <string name="peerTtyModeHco" msgid="5626377160840915617">"पीयर ने टेलीटाइपराइटर (TTY) मोड एचसीओ (HCO) का अनुरोध किया"</string> <string name="peerTtyModeVco" msgid="572208600818270944">"पीयर ने टेलीटाइपराइटर (TTY) मोड वीसीओ (VCO) का अनुरोध किया"</string> <string name="peerTtyModeOff" msgid="2420380956369226583">"पीयर ने टेलीटाइपराइटर (TTY) मोड बंद का अनुरोध किया"</string> - <string name="serviceClassVoice" msgid="2065556932043454987">"आवाज़"</string> + <string name="serviceClassVoice" msgid="2065556932043454987">"Voice"</string> <string name="serviceClassData" msgid="4148080018967300248">"डेटा"</string> <string name="serviceClassFAX" msgid="2561653371698904118">"फ़ैक्स"</string> <string name="serviceClassSMS" msgid="1547664561704509004">"मैसेज (एसएमएस)"</string> @@ -1166,7 +1166,7 @@ <string name="clearDefaultHintMsg" msgid="1325866337702524936">"सिस्टम सेटिंग और डाउनलोड किए गए ऐप में डिफ़ॉल्ट साफ़ करें."</string> <string name="chooseActivity" msgid="8563390197659779956">"कोई कार्रवाई चुनें"</string> <string name="chooseUsbActivity" msgid="2096269989990986612">"USB डिवाइस के लिए कोई ऐप्स चुनें"</string> - <string name="noApplications" msgid="1186909265235544019">"कोई भी ऐप्स यह कार्यवाही नहीं कर सकता."</string> + <string name="noApplications" msgid="1186909265235544019">"कोई भी ऐप्लिकेशन यह कार्रवाई नहीं कर सकता."</string> <string name="aerr_application" msgid="4090916809370389109">"<xliff:g id="APPLICATION">%1$s</xliff:g> रुक गया है"</string> <string name="aerr_process" msgid="4268018696970966407">"<xliff:g id="PROCESS">%1$s</xliff:g> रुक गई है"</string> <string name="aerr_application_repeated" msgid="7804378743218496566">"<xliff:g id="APPLICATION">%1$s</xliff:g> रुक रहा है"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index af282c0b93c8..625b3eda77b3 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1340,7 +1340,7 @@ <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Otkriven je analogni audiododatak"</string> <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Priključeni uređaj nije kompatibilan s ovim telefonom. Dodirnite da biste saznali više."</string> <string name="adb_active_notification_title" msgid="408390247354560331">"Priključen je alat za otklanjanje pogrešaka putem USB-a"</string> - <string name="adb_active_notification_message" msgid="5617264033476778211">"Dodirnite da isključite otklanjanje pogrešaka putem USB-a"</string> + <string name="adb_active_notification_message" msgid="5617264033476778211">"Dodirnite da isključite otkl. pogrešaka putem USB-a"</string> <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Odaberite da biste onemogućili rješavanje programske pogreške na USB-u."</string> <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Bežično otklanjanje pogrešaka povezano"</string> <string name="adbwifi_active_notification_message" msgid="930987922852867972">"Dodirnite da biste isključili bežično otklanjanje pogrešaka"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 5672f46ffa6c..be3e3c75d958 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -864,7 +864,7 @@ <string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"הרצועה הקודמת"</string> <string name="lockscreen_transport_next_description" msgid="2931509904881099919">"הרצועה הבאה"</string> <string name="lockscreen_transport_pause_description" msgid="6705284702135372494">"השהה"</string> - <string name="lockscreen_transport_play_description" msgid="106868788691652733">"הפעל"</string> + <string name="lockscreen_transport_play_description" msgid="106868788691652733">"הפעלה"</string> <string name="lockscreen_transport_stop_description" msgid="1449552232598355348">"הפסק"</string> <string name="lockscreen_transport_rew_description" msgid="7680106856221622779">"הרץ אחורה"</string> <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"הרץ קדימה"</string> @@ -1852,7 +1852,7 @@ <string name="battery_saver_description" msgid="6794188153647295212">"כדי להאריך את חיי הסוללה, התכונה \'חיסכון בסוללה\':\n\n• מפעילה עיצוב כהה\n• מכבה או מגבילה פעילות ברקע, חלק מהאפקטים החזותיים ותכונות אחרות כמו Ok Google"</string> <string name="data_saver_description" msgid="4995164271550590517">"כדי לסייע בהפחתת השימוש בנתונים, חוסך הנתונים (Data Saver) מונע מאפליקציות מסוימות שליחה או קבלה של נתונים ברקע. אפליקציה שבה נעשה שימוש כרגע יכולה לגשת לנתונים, אבל בתדירות נמוכה יותר. המשמעות היא, למשל, שתמונות יוצגו רק לאחר שמקישים עליהן."</string> <string name="data_saver_enable_title" msgid="7080620065745260137">"להפעיל את חוסך הנתונים?"</string> - <string name="data_saver_enable_button" msgid="4399405762586419726">"הפעל"</string> + <string name="data_saver_enable_button" msgid="4399405762586419726">"הפעלה"</string> <plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="2877101784123058273"> <item quantity="two">למשך %d דקות (עד <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item> <item quantity="many">למשך %1$d דקות (עד <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item> @@ -1963,7 +1963,7 @@ <string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"ביטול ההשהיה של האפליקציה"</string> <string name="work_mode_off_title" msgid="5503291976647976560">"להפעיל את פרופיל העבודה?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"אפליקציות העבודה, התראות, נתונים ותכונות נוספות של פרופיל העבודה יופעלו"</string> - <string name="work_mode_turn_on" msgid="3662561662475962285">"הפעל"</string> + <string name="work_mode_turn_on" msgid="3662561662475962285">"הפעלה"</string> <string name="app_blocked_title" msgid="7353262160455028160">"האפליקציה לא זמינה"</string> <string name="app_blocked_message" msgid="542972921087873023">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> לא זמינה בשלב זה."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"האפליקציה הזו עוצבה לגרסה ישנה יותר של Android וייתכן שלא תפעל כראוי. ניתן לבדוק אם יש עדכונים או ליצור קשר עם המפתח."</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 937645ea0f05..b7356e175c6b 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -100,7 +100,7 @@ <string name="peerTtyModeHco" msgid="5626377160840915617">"ಪೀರ್ ವಿನಂತಿಸಿಕೊಂಡ TTY ಮೋಡ್ HCO"</string> <string name="peerTtyModeVco" msgid="572208600818270944">"ಪೀರ್ ವಿನಂತಿಸಿಕೊಂಡ TTY ಮೋಡ್ VCO"</string> <string name="peerTtyModeOff" msgid="2420380956369226583">"ಪೀರ್ ವಿನಂತಿಸಿಕೊಂಡ TTY ಮೋಡ್ ಆಫ್ ಆಗಿದೆ"</string> - <string name="serviceClassVoice" msgid="2065556932043454987">"ಧ್ವನಿ"</string> + <string name="serviceClassVoice" msgid="2065556932043454987">"Voice"</string> <string name="serviceClassData" msgid="4148080018967300248">"ಡೇಟಾ"</string> <string name="serviceClassFAX" msgid="2561653371698904118">"ಫ್ಯಾಕ್ಸ್"</string> <string name="serviceClassSMS" msgid="1547664561704509004">"SMS"</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index e7a16282bf46..30c0d03350fd 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -100,7 +100,7 @@ <string name="peerTtyModeHco" msgid="5626377160840915617">"പിയർ അഭ്യർത്ഥിച്ച TTY മോഡ് HCO"</string> <string name="peerTtyModeVco" msgid="572208600818270944">"പിയർ അഭ്യർത്ഥിച്ച TTY മോഡ് VCO"</string> <string name="peerTtyModeOff" msgid="2420380956369226583">"പിയർ അഭ്യർത്ഥിച്ച TTY മോഡ് \'ഓഫ്\'"</string> - <string name="serviceClassVoice" msgid="2065556932043454987">"ശബ്ദം"</string> + <string name="serviceClassVoice" msgid="2065556932043454987">"Voice"</string> <string name="serviceClassData" msgid="4148080018967300248">"ഡാറ്റ"</string> <string name="serviceClassFAX" msgid="2561653371698904118">"ഫാക്സ്"</string> <string name="serviceClassSMS" msgid="1547664561704509004">"SMS"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 14615fb62b1c..b65a31667ce0 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -100,7 +100,7 @@ <string name="peerTtyModeHco" msgid="5626377160840915617">"समवयस्क व्यक्तीने TTY मोड HCO ची विनंती केली"</string> <string name="peerTtyModeVco" msgid="572208600818270944">"समवयस्क व्यक्तीने TTY मोड VCO ची विनंती केली"</string> <string name="peerTtyModeOff" msgid="2420380956369226583">"समवयस्क व्यक्तीने TTY मोड बंद ची विनंती केली"</string> - <string name="serviceClassVoice" msgid="2065556932043454987">"व्हॉइस"</string> + <string name="serviceClassVoice" msgid="2065556932043454987">"Voice"</string> <string name="serviceClassData" msgid="4148080018967300248">"डेटा"</string> <string name="serviceClassFAX" msgid="2561653371698904118">"फॅक्स"</string> <string name="serviceClassSMS" msgid="1547664561704509004">"SMS"</string> @@ -2009,14 +2009,10 @@ <string name="notification_appops_microphone_active" msgid="581333393214739332">"मायक्रोफोन"</string> <string name="notification_appops_overlay_active" msgid="5571732753262836481">"तुमच्या स्क्रीनवर इतर ॲप्सवर डिस्प्ले करत आहे"</string> <string name="notification_feedback_indicator" msgid="663476517711323016">"फीडबॅक द्या"</string> - <!-- no translation found for notification_feedback_indicator_alerted (6552871804121942099) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_silenced (3799442124723177262) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_promoted (9030204303764698640) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_demoted (8880309924296450875) --> - <skip /> + <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"ही सूचना डीफॉल्ट करण्यात आली आहे. फीडबॅक देण्यासाठी टॅप करा."</string> + <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"ही सूचना सायलंट करण्यात आली आहे. फीडबॅक देण्यासाठी टॅप करा."</string> + <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"हा सूचनेला उच्च रँक करण्यात आले. फीडबॅक देण्यासाठी टॅप करा."</string> + <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"या सूचनेला कमी रँक करण्यात आले. फीडबॅक देण्यासाठी टॅप करा."</string> <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"दिनक्रम मोडची माहिती सूचना"</string> <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"चार्जिंगची सामान्य पातळी गाठेपर्यंत कदाचित बॅटरी संपू शकते"</string> <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"बॅटरी लाइफ वाढवण्यासाठी बॅटरी सेव्हर सुरू केला आहे"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index 90d4d84cc7df..e53a519cc2a1 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -2009,14 +2009,10 @@ <string name="notification_appops_microphone_active" msgid="581333393214739332">"माइक्रोफोन"</string> <string name="notification_appops_overlay_active" msgid="5571732753262836481">"तपाईंको स्क्रिनका अन्य एपहरूमा प्रदर्शन गरिँदै छ"</string> <string name="notification_feedback_indicator" msgid="663476517711323016">"प्रतिक्रिया दिनुहोस्"</string> - <!-- no translation found for notification_feedback_indicator_alerted (6552871804121942099) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_silenced (3799442124723177262) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_promoted (9030204303764698640) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_demoted (8880309924296450875) --> - <skip /> + <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"सिस्टमले स्वतः यस सूचनालाई महत्त्वपूर्ण ठानी यसका लागि पूर्वनिर्धारित मोड सेट गरिदिएको छ। प्रतिक्रिया दिन ट्याप गर्नुहोस्।"</string> + <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"यस सूचनालाई कम महत्त्वपूर्ण ठानी यसका लागि साइलेन्ट मोड सेट गरिएको छ। प्रतिक्रिया दिन ट्याप गर्नुहोस्।"</string> + <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"यस सूचनालाई धेरै महत्त्वपूर्ण सूचनाका रूपमा सेट गरिएको छ। प्रतिक्रिया दिन ट्याप गर्नुहोस्।"</string> + <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"यस सूचनालाई कम महत्त्वपूर्ण सूचनाका रूपमा सेट गरिएको छ। प्रतिक्रिया दिन ट्याप गर्नुहोस्।"</string> <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"दिनचर्या मोडको जानकारीमूलक सूचना"</string> <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"प्रायः चार्ज गर्ने समय हुनुभन्दा पहिले नै ब्याट्री सकिन सक्छ"</string> <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"ब्याट्रीको आयु बढाउन ब्याट्री सेभर सक्रिय गरियो"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index b5935e97b238..2124d49474b4 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -100,7 +100,7 @@ <string name="peerTtyModeHco" msgid="5626377160840915617">"ପୀଅର୍ ଅନୁରୋଧ କରିଥିବା TTY ମୋଡ୍ HCO ଅଟେ"</string> <string name="peerTtyModeVco" msgid="572208600818270944">"ପୀଅର୍ ଅନୁରୋଧ କରିଥିବା TTY ମୋଡ୍ VCO ଅଟେ"</string> <string name="peerTtyModeOff" msgid="2420380956369226583">"ପୀଅର୍ ଅନୁରୋଧ କରିଥିବା TTY ମୋଡ୍ OFF ଅଛି"</string> - <string name="serviceClassVoice" msgid="2065556932043454987">"ଭଏସ୍"</string> + <string name="serviceClassVoice" msgid="2065556932043454987">"Voice"</string> <string name="serviceClassData" msgid="4148080018967300248">"ଡାଟା"</string> <string name="serviceClassFAX" msgid="2561653371698904118">"ଫାକ୍ସ"</string> <string name="serviceClassSMS" msgid="1547664561704509004">"SMS"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 1f53964c1bc1..4518a3bfe535 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -1319,7 +1319,7 @@ <string name="usb_power_notification_message" msgid="7284765627437897702">"ਕਨੈਕਟ ਕੀਤਾ ਡੀਵਾਈਸ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ। ਹੋਰ ਵਿਕਲਪਾਂ ਲਈ ਟੈਪ ਕਰੋ।"</string> <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"ਐਨਾਲੌਗ ਆਡੀਓ ਉਪਸਾਧਨ ਦਾ ਪਤਾ ਲੱਗਿਆ"</string> <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"ਨੱਥੀ ਕੀਤਾ ਡੀਵਾਈਸ ਇਸ ਫ਼ੋਨ ਦੇ ਅਨੁਰੂਪ ਨਹੀਂ ਹੈ। ਹੋਰ ਜਾਣਨ ਲਈ ਟੈਪ ਕਰੋ।"</string> - <string name="adb_active_notification_title" msgid="408390247354560331">"USB ਡੀਬਗਿੰਗ ਕਨੈਕਟ ਕੀਤੀ"</string> + <string name="adb_active_notification_title" msgid="408390247354560331">"USB ਡੀਬੱਗਿੰਗ ਕਨੈਕਟ ਕੀਤੀ"</string> <string name="adb_active_notification_message" msgid="5617264033476778211">"USB ਡੀਬੱਗਿੰਗ ਬੰਦ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"USB ਡੀਬੱਗਿੰਗ ਅਯੋਗ ਬਣਾਉਣ ਲਈ ਚੁਣੋ।"</string> <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"ਵਾਇਰਲੈੱਸ ਡੀਬੱਗਿੰਗ ਨੂੰ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string> @@ -2009,14 +2009,10 @@ <string name="notification_appops_microphone_active" msgid="581333393214739332">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ"</string> <string name="notification_appops_overlay_active" msgid="5571732753262836481">"ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਹੋਰਾਂ ਐਪਾਂ ਉੱਪਰ ਦਿਖਾਇਆ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="notification_feedback_indicator" msgid="663476517711323016">"ਵਿਚਾਰ ਦਿਓ"</string> - <!-- no translation found for notification_feedback_indicator_alerted (6552871804121942099) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_silenced (3799442124723177262) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_promoted (9030204303764698640) --> - <skip /> - <!-- no translation found for notification_feedback_indicator_demoted (8880309924296450875) --> - <skip /> + <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"ਇਸ ਸੂਚਨਾ ਦਾ ਦਰਜਾ ਵਧਾ ਕੇ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ \'ਤੇ ਸੈੱਟ ਕੀਤਾ ਗਿਆ। ਵਿਚਾਰ ਮੁਹੱਈਆ ਕਰਵਾਉਣ ਲਈ ਟੈਪ ਕਰੋ।"</string> + <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"ਇਸ ਸੂਚਨਾ ਦਾ ਦਰਜਾ ਘਟਾ ਕੇ ਸ਼ਾਂਤ \'ਤੇ ਸੈੱਟ ਕੀਤਾ ਗਿਆ। ਵਿਚਾਰ ਮੁਹੱਈਆ ਕਰਵਾਉਣ ਲਈ ਟੈਪ ਕਰੋ।"</string> + <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"ਇਸ ਸੂਚਨਾ ਦਾ ਦਰਜਾ ਵਧਾ ਦਿੱਤਾ ਗਿਆ। ਵਿਚਾਰ ਮੁਹੱਈਆ ਕਰਵਾਉਣ ਲਈ ਟੈਪ ਕਰੋ।"</string> + <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"ਇਸ ਸੂਚਨਾ ਦਾ ਦਰਜਾ ਘਟਾ ਦਿੱਤਾ ਗਿਆ। ਵਿਚਾਰ ਮੁਹੱਈਆ ਕਰਵਾਉਣ ਲਈ ਟੈਪ ਕਰੋ।"</string> <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"ਨਿਯਮਬੱਧ ਮੋਡ ਦੀ ਜਾਣਕਾਰੀ ਵਾਲੀ ਸੂਚਨਾ"</string> <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"ਬੈਟਰੀ ਚਾਰਜ ਕਰਨ ਦੇ ਮਿੱਥੇ ਸਮੇਂ ਤੋਂ ਪਹਿਲਾਂ ਸ਼ਾਇਦ ਬੈਟਰੀ ਖਤਮ ਹੋ ਜਾਵੇ"</string> <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"ਬੈਟਰੀ ਲਾਈਫ਼ ਵਧਾਉਣ ਲਈ ਬੈਟਰੀ ਸੇਵਰ ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 02d29cb82915..cfcc2d529e7b 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1848,8 +1848,8 @@ <string name="package_updated_device_owner" msgid="7560272363805506941">"Zaktualizowany przez administratora"</string> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Usunięty przez administratora"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> - <string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Aby wydłużyć czas pracy na baterii, funkcja Oszczędzanie baterii:\n\n• włącza tryb ciemny,\n• wyłącza lub ogranicza aktywność w tle, niektóre efekty wizualne oraz inne funkcje, np. „OK Google”.\n\n"<annotation id="url">"Więcej informacji"</annotation></string> - <string name="battery_saver_description" msgid="6794188153647295212">"Aby wydłużyć czas pracy na baterii, Oszczędzanie baterii:\n\n• włącza tryb ciemny,\n• wyłącza lub ogranicza aktywność w tle, niektóre efekty wizualne oraz inne funkcje, np. „OK Google”."</string> + <string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Aby wydłużyć czas pracy na baterii, funkcja Oszczędzanie baterii:\n\n• włącza ciemny motyw,\n• wyłącza lub ogranicza aktywność w tle, niektóre efekty wizualne oraz inne funkcje, np. „OK Google”.\n\n"<annotation id="url">"Więcej informacji"</annotation></string> + <string name="battery_saver_description" msgid="6794188153647295212">"Aby wydłużyć czas pracy na baterii, Oszczędzanie baterii:\n\n• włącza ciemny motyw,\n• wyłącza lub ogranicza aktywność w tle, niektóre efekty wizualne oraz inne funkcje, np. „OK Google”."</string> <string name="data_saver_description" msgid="4995164271550590517">"Oszczędzanie danych uniemożliwia niektórym aplikacjom wysyłanie i odbieranie danych w tle, zmniejszając w ten sposób ich użycie. Aplikacja, z której w tej chwili korzystasz, może uzyskiwać dostęp do danych, ale rzadziej. Może to powodować, że obrazy będą się wyświetlać dopiero po kliknięciu."</string> <string name="data_saver_enable_title" msgid="7080620065745260137">"Włączyć Oszczędzanie danych?"</string> <string name="data_saver_enable_button" msgid="4399405762586419726">"Włącz"</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 4f0407b7cd9f..218412bed05c 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -1319,8 +1319,8 @@ <string name="usb_power_notification_message" msgid="7284765627437897702">"Pajisja e lidhur po karikohet. Trokit për opsione të tjera."</string> <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"U zbulua aksesor i audios analoge"</string> <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Pajisja e bashkuar nuk është e pajtueshme me këtë telefon. Trokit për të mësuar më shumë."</string> - <string name="adb_active_notification_title" msgid="408390247354560331">"Korrigjuesi i USB-së është i lidhur"</string> - <string name="adb_active_notification_message" msgid="5617264033476778211">"Trokit për të çaktivizuar korrigjimin e USB-së"</string> + <string name="adb_active_notification_title" msgid="408390247354560331">"Korrigjimi përmes USB-së është i lidhur"</string> + <string name="adb_active_notification_message" msgid="5617264033476778211">"Trokit për të çaktivizuar korrigjimin përmes USB-së"</string> <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Përzgjidhe për të çaktivizuar korrigjimin e gabimeve të USB-së"</string> <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Korrigjimi përmes Wi-Fi është lidhur"</string> <string name="adbwifi_active_notification_message" msgid="930987922852867972">"Trokit për të çaktivizuar korrigjimin përmes Wi-Fi"</string> @@ -1931,7 +1931,7 @@ <string name="app_category_maps" msgid="6395725487922533156">"Harta dhe navigim"</string> <string name="app_category_productivity" msgid="1844422703029557883">"Produktivitet"</string> <string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"Hapësira ruajtëse e pajisjes"</string> - <string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"Korrigjimi i USB-së"</string> + <string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"Korrigjimi përmes USB-së"</string> <string name="time_picker_hour_label" msgid="4208590187662336864">"orë"</string> <string name="time_picker_minute_label" msgid="8307452311269824553">"minutë"</string> <string name="time_picker_header_text" msgid="9073802285051516688">"Vendos orën"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 1342dbea141d..ec9c0a73ec88 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -100,7 +100,7 @@ <string name="peerTtyModeHco" msgid="5626377160840915617">"TTY Mode HCOஐ இணைச் செயல்பாடு கோரியது"</string> <string name="peerTtyModeVco" msgid="572208600818270944">"TTY Mode VCOஐ இணைச் செயல்பாடு கோரியது"</string> <string name="peerTtyModeOff" msgid="2420380956369226583">"TTY Mode OFFஐ இணைச் செயல்பாடு கோரியது"</string> - <string name="serviceClassVoice" msgid="2065556932043454987">"குரல்"</string> + <string name="serviceClassVoice" msgid="2065556932043454987">"Voice"</string> <string name="serviceClassData" msgid="4148080018967300248">"தரவு"</string> <string name="serviceClassFAX" msgid="2561653371698904118">"தொலைநகல்"</string> <string name="serviceClassSMS" msgid="1547664561704509004">"SMS"</string> @@ -270,7 +270,7 @@ <string name="global_action_lockdown" msgid="2475471405907902963">"பூட்டு"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"புதிய அறிவிப்பு"</string> - <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"விர்ச்சுவல் கீபோர்ட்"</string> + <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"விர்ச்சுவல் கீபோர்டு"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"கைமுறை விசைப்பலகை"</string> <string name="notification_channel_security" msgid="8516754650348238057">"பாதுகாப்பு"</string> <string name="notification_channel_car_mode" msgid="2123919247040988436">"கார் பயன்முறை"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 0f5d000bc635..6483db1ea8cb 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -100,7 +100,7 @@ <string name="peerTtyModeHco" msgid="5626377160840915617">"అవతలి వారు HCO TTY మోడ్ని అభ్యర్థించారు"</string> <string name="peerTtyModeVco" msgid="572208600818270944">"అవతలి వారు VCO TTY మోడ్ని అభ్యర్థించారు"</string> <string name="peerTtyModeOff" msgid="2420380956369226583">"అవతలి వారు OFF TTY మోడ్ని అభ్యర్థించారు"</string> - <string name="serviceClassVoice" msgid="2065556932043454987">"వాయిస్"</string> + <string name="serviceClassVoice" msgid="2065556932043454987">"Voice"</string> <string name="serviceClassData" msgid="4148080018967300248">"డేటా"</string> <string name="serviceClassFAX" msgid="2561653371698904118">"ఫ్యాక్స్"</string> <string name="serviceClassSMS" msgid="1547664561704509004">"SMS"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index abf116c80d15..094b6c8cbbe4 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -314,7 +314,7 @@ <string name="permgrouplab_camera" msgid="9090413408963547706">"กล้องถ่ายรูป"</string> <string name="permgroupdesc_camera" msgid="7585150538459320326">"ถ่ายภาพและบันทึกวิดีโอ"</string> <string name="permgrouplab_calllog" msgid="7926834372073550288">"ประวัติการโทร"</string> - <string name="permgroupdesc_calllog" msgid="2026996642917801803">"อ่านและเขียนประวัติการโทรของโทรศัพท์"</string> + <string name="permgroupdesc_calllog" msgid="2026996642917801803">"อ่านและเขียนบันทึกการโทรของโทรศัพท์"</string> <string name="permgrouplab_phone" msgid="570318944091926620">"โทรศัพท์"</string> <string name="permgroupdesc_phone" msgid="270048070781478204">"โทรและจัดการการโทร"</string> <string name="permgrouplab_sensors" msgid="9134046949784064495">"เซ็นเซอร์ร่างกาย"</string> @@ -409,12 +409,12 @@ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"อนุญาตให้แอปแก้ไขข้อมูลเกี่ยวกับรายชื่อติดต่อที่จัดเก็บไว้ในแท็บเล็ต สิทธิ์นี้ทำให้แอปลบข้อมูลรายชื่อติดต่อได้"</string> <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"อนุญาตให้แอปแก้ไขข้อมูลเกี่ยวกับรายชื่อติดต่อที่จัดเก็บไว้ในอุปกรณ์ Android TV สิทธิ์นี้ทำให้แอปลบข้อมูลรายชื่อติดต่อได้"</string> <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"อนุญาตให้แอปแก้ไขข้อมูลเกี่ยวกับรายชื่อติดต่อที่จัดเก็บไว้ในโทรศัพท์ สิทธิ์นี้ทำให้แอปลบข้อมูลรายชื่อติดต่อได้"</string> - <string name="permlab_readCallLog" msgid="1739990210293505948">"อ่านประวัติการโทร"</string> + <string name="permlab_readCallLog" msgid="1739990210293505948">"อ่านบันทึกการโทร"</string> <string name="permdesc_readCallLog" msgid="8964770895425873433">"แอปนี้สามารถอ่านประวัติการโทรของคุณได้"</string> - <string name="permlab_writeCallLog" msgid="670292975137658895">"เขียนประวัติการโทร"</string> - <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"อนุญาตให้แอปแก้ไขประวัติการโทรจากแท็บเล็ตของคุณ รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและการโทรออก แอปที่เป็นอันตรายอาจใช้สิ่งนี้เพื่อลบหรือแก้ไขประวัติการโทรของคุณ"</string> - <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"อนุญาตให้แอปแก้ไขประวัติการโทรจากอุปกรณ์ Android TV รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและสายโทรออก แอปที่เป็นอันตรายอาจใช้สิทธิ์นี้เพื่อลบหรือแก้ไขประวัติการโทรได้"</string> - <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"อนุญาตให้แอปแก้ไขประวัติการโทรจากโทรศัพท์ของคุณ รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและการโทรออก แอปที่เป็นอันตรายอาจใช้สิ่งนี้เพื่อลบหรือแก้ไขประวัติการโทรของคุณ"</string> + <string name="permlab_writeCallLog" msgid="670292975137658895">"เขียนบันทึกการโทร"</string> + <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"อนุญาตให้แอปแก้ไขบันทึกการโทรจากแท็บเล็ตของคุณ รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและการโทรออก แอปที่เป็นอันตรายอาจใช้สิ่งนี้เพื่อลบหรือแก้ไขบันทึกการโทรของคุณ"</string> + <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"อนุญาตให้แอปแก้ไขบันทึกการโทรจากอุปกรณ์ Android TV รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและสายโทรออก แอปที่เป็นอันตรายอาจใช้สิทธิ์นี้เพื่อลบหรือแก้ไขบันทึกการโทรได้"</string> + <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"อนุญาตให้แอปแก้ไขบันทึกการโทรจากโทรศัพท์ของคุณ รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและการโทรออก แอปที่เป็นอันตรายอาจใช้สิ่งนี้เพื่อลบหรือแก้ไขบันทึกการโทรของคุณ"</string> <string name="permlab_bodySensors" msgid="3411035315357380862">"เข้าถึงเซ็นเซอร์ร่างกาย (เช่น ตัววัดอัตราการเต้นของหัวใจ)"</string> <string name="permdesc_bodySensors" product="default" msgid="2365357960407973997">"อนุญาตให้แอปเข้าถึงข้อมูลจากเซ็นเซอร์ที่ตรวจสอบสภาพทางกายภาพ เช่น อัตราการเต้นของหัวใจ"</string> <string name="permlab_readCalendar" msgid="6408654259475396200">"อ่านกิจกรรมในปฏิทินและรายละเอียด"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 258b4249c1e4..af10694a729e 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -2009,8 +2009,8 @@ <string name="notification_appops_microphone_active" msgid="581333393214739332">"Mikrofon"</string> <string name="notification_appops_overlay_active" msgid="5571732753262836481">"ekranınızdaki diğer uygulamaların üzerinde görüntüleniyor"</string> <string name="notification_feedback_indicator" msgid="663476517711323016">"Geri Bildirim Gönder"</string> - <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Bu bildirim, Varsayılana yükseltildi. Geri bildirimde bulunmak için dokunun."</string> - <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Bu bildirim Sessize düşürüldü. Geri bildirimde bulunmak için dokunun."</string> + <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Bu bildirimin önem derecesi, \"Varsayılan\" seviyesine yükseltildi. Geri bildirimde bulunmak için dokunun."</string> + <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Bu bildirimin önem derecesi, \"Sessiz\" seviyesine düşürüldü. Geri bildirimde bulunmak için dokunun."</string> <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Bu bildirimin önem derecesi yükseltildi. Geri bildirimde bulunmak için dokunun."</string> <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Bu bildirimin önem derecesi düşürüldü. Geri bildirimde bulunmak için dokunun."</string> <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Rutin Modu bilgi bildirimi"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 04498c1edae6..37cb12a7d817 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -829,7 +829,7 @@ <string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"PUK اور نیا PIN کوڈ ٹائپ کریں"</string> <string name="keyguard_password_enter_puk_prompt" msgid="2825313071899938305">"PUK کوڈ"</string> <string name="keyguard_password_enter_pin_prompt" msgid="5505434724229581207">"نیا PIN کوڈ"</string> - <string name="keyguard_password_entry_touch_hint" msgid="4032288032993261520"><font size="17">"پاسورڈ ٹائپ کرنے کیلئے تھپتھپائیں"</font></string> + <string name="keyguard_password_entry_touch_hint" msgid="4032288032993261520"><font size="17">"پاس ورڈ ٹائپ کرنے کیلئے تھپتھپائیں"</font></string> <string name="keyguard_password_enter_password_code" msgid="2751130557661643482">"غیر مقفل کرنے کیلئے پاس ورڈ ٹائپ کریں"</string> <string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"غیر مقفل کرنے کیلئے PIN ٹائپ کریں"</string> <string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"غلط PIN کوڈ۔"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 11e20a9060bf..23fd462be7b0 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -1410,7 +1410,7 @@ <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Ilovaga batareya quvvatidan xohlagancha foydalanish uchun ruxsat so‘rashga imkon beradi."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Ko‘lamini o‘zgartirish uchun ikki marta bosing"</string> <string name="gadget_host_error_inflating" msgid="2449961590495198720">"Vidjet qo‘shilmadi."</string> - <string name="ime_action_go" msgid="5536744546326495436">"O‘tish"</string> + <string name="ime_action_go" msgid="5536744546326495436">"Tanlash"</string> <string name="ime_action_search" msgid="4501435960587287668">"Qidirish"</string> <string name="ime_action_send" msgid="8456843745664334138">"Yuborish"</string> <string name="ime_action_next" msgid="4169702997635728543">"Keyingisi"</string> @@ -2009,10 +2009,10 @@ <string name="notification_appops_microphone_active" msgid="581333393214739332">"Mikrofon"</string> <string name="notification_appops_overlay_active" msgid="5571732753262836481">"ekranda boshqa ilovalar ustidan ochiladi"</string> <string name="notification_feedback_indicator" msgid="663476517711323016">"Fikr-mulohaza yuborish"</string> - <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Bu bildirishnoma standart sifatida balandlatildi. Fikr-mulohaza bildirish uchun bosing."</string> - <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Bu bildirishnoma Ovozsiz sifatida pastlatildi Fikr-mulohaza bildirish uchun bosing."</string> - <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Bu bildirishnomaga baland baho berilgan. Fikr-mulohaza bildirish uchun bosing."</string> - <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Bu bildirishnomaga past baho berilgan. Fikr-mulohaza bildirish uchun bosing."</string> + <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Bu bildirishnoma darajasi Standart darajaga chiqarildi. Fikr-mulohaza bildirish uchun bosing."</string> + <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Bu bildirishnoma darajasi Tovushsiz darajaga tushirildi Fikr-mulohaza bildirish uchun bosing."</string> + <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Bu bildirishnoma darajasi oshirildi. Fikr-mulohaza bildirish uchun bosing."</string> + <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Bu bildirishnoma darajasi pasaytirildi. Fikr-mulohaza bildirish uchun bosing."</string> <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Kun tartibi rejimi haqidagi bildirishnoma"</string> <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Batareya quvvati odatdagidan ertaroq tugashi mumkin"</string> <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Batareya quvvatini uzoqroq vaqtga yetkazish uchun quvvat tejash rejimi yoqildi"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 7ca3fafdca47..ef54db1a422c 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 da658cc3d525..cff1bdaa4477 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3724,6 +3724,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/dimens.xml b/core/res/res/values/dimens.xml index 5b19a486881a..3ae21311ea49 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -66,6 +66,10 @@ <!-- The height of the navigation gesture area if the gesture is starting from the bottom. --> <dimen name="navigation_bar_gesture_height">@dimen/navigation_bar_frame_height</dimen> + <!-- The height of the navigation larger gesture area if the gesture is starting from + the bottom. --> + <dimen name="navigation_bar_gesture_larger_height">80dp</dimen> + <!-- Height of the bottom navigation / system bar in car mode. --> <dimen name="navigation_bar_height_car_mode">96dp</dimen> <!-- Height of the bottom navigation bar in portrait; often the same as @@ -301,12 +305,18 @@ <!-- The top padding for the notification expand button. --> <dimen name="notification_expand_button_padding_top">1dp</dimen> - <!-- minimum vertical margin for the headerless notification content --> + <!-- minimum vertical margin for the headerless notification content, when cap = 60dp --> <dimen name="notification_headerless_margin_minimum">8dp</dimen> - <!-- extra vertical margin for the headerless notification content --> + <!-- extra vertical margin for the headerless notification content, when cap = 60dp --> <dimen name="notification_headerless_margin_extra">10dp</dimen> + <!-- minimum vertical margin for the headerless notification content, when cap = 48dp --> + <dimen name="notification_headerless_margin_constrained_minimum">14dp</dimen> + + <!-- extra vertical margin for the headerless notification content, when cap = 48dp --> + <dimen name="notification_headerless_margin_constrained_extra">4dp</dimen> + <!-- The height of each of the 1 or 2 lines in the headerless notification template --> <dimen name="notification_headerless_line_height">20sp</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index fa664519152b..5b4294717e27 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1702,6 +1702,7 @@ <java-symbol type="dimen" name="navigation_bar_frame_height" /> <java-symbol type="dimen" name="navigation_bar_frame_height_landscape" /> <java-symbol type="dimen" name="navigation_bar_gesture_height" /> + <java-symbol type="dimen" name="navigation_bar_gesture_larger_height" /> <java-symbol type="dimen" name="navigation_bar_height_car_mode" /> <java-symbol type="dimen" name="navigation_bar_height_landscape_car_mode" /> <java-symbol type="dimen" name="navigation_bar_width_car_mode" /> @@ -2876,6 +2877,8 @@ <java-symbol type="id" name="alternate_expand_target" /> <java-symbol type="id" name="notification_header" /> <java-symbol type="id" name="notification_top_line" /> + <java-symbol type="id" name="notification_headerless_margin_extra_top" /> + <java-symbol type="id" name="notification_headerless_margin_extra_bottom" /> <java-symbol type="id" name="time_divider" /> <java-symbol type="id" name="header_text_divider" /> <java-symbol type="id" name="header_text_secondary_divider" /> @@ -2897,6 +2900,8 @@ <java-symbol type="dimen" name="notification_header_icon_size" /> <java-symbol type="dimen" name="notification_header_app_name_margin_start" /> <java-symbol type="dimen" name="notification_header_separating_margin" /> + <java-symbol type="dimen" name="notification_headerless_margin_constrained_minimum" /> + <java-symbol type="dimen" name="notification_headerless_margin_constrained_extra" /> <java-symbol type="string" name="default_notification_channel_label" /> <java-symbol type="string" name="importance_from_user" /> <java-symbol type="string" name="importance_from_person" /> @@ -3479,6 +3484,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/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index c0731c82bff3..dff6e874c9b0 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -361,6 +361,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1119,6 +1121,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <!-- Progress bar attributes --> <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item> 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 133efce58b70..aab922972c54 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/app/assist/AssistStructureTest.java b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java index da386a6bac90..4609c23bbbd1 100644 --- a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java +++ b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java @@ -23,11 +23,14 @@ import static android.view.View.IMPORTANT_FOR_AUTOFILL_YES; import static com.google.common.truth.Truth.assertThat; import android.app.assist.AssistStructure.ViewNode; +import android.app.assist.AssistStructure.ViewNodeBuilder; +import android.app.assist.AssistStructure.ViewNodeParcelable; import android.content.Context; import android.os.Parcel; import android.os.SystemClock; import android.text.InputFilter; import android.util.Log; +import android.view.View; import android.view.autofill.AutofillId; import android.widget.EditText; import android.widget.FrameLayout; @@ -219,6 +222,28 @@ public class AssistStructureTest { } } + @Test + public void testViewNodeParcelableForAutofill() { + Log.d(TAG, "Adding view with " + BIG_VIEW_SIZE + " chars"); + + View view = newBigView(); + mActivity.addView(view); + waitUntilViewsAreLaidOff(); + + assertThat(view.getViewRootImpl()).isNotNull(); + ViewNodeBuilder viewStructure = new ViewNodeBuilder(); + viewStructure.setAutofillId(view.getAutofillId()); + view.onProvideAutofillStructure(viewStructure, /* flags= */ 0); + ViewNodeParcelable viewNodeParcelable = new ViewNodeParcelable(viewStructure.getViewNode()); + + // Check properties on "original" view node. + assertBigView(viewNodeParcelable.getViewNode()); + + // Check properties on "cloned" view node. + ViewNodeParcelable clone = cloneThroughParcel(viewNodeParcelable); + assertBigView(clone.getViewNode()); + } + private EditText newSmallView() { EditText view = new EditText(mContext); view.setText("I AM GROOT"); @@ -272,6 +297,24 @@ public class AssistStructureTest { assertThat(hint.charAt(BIG_VIEW_SIZE - 1)).isEqualTo(BIG_VIEW_CHAR); } + private ViewNodeParcelable cloneThroughParcel(ViewNodeParcelable viewNodeParcelable) { + Parcel parcel = Parcel.obtain(); + + try { + // Write to parcel + parcel.setDataPosition(0); // Validity Check + viewNodeParcelable.writeToParcel(parcel, NO_FLAGS); + + // Read from parcel + parcel.setDataPosition(0); + ViewNodeParcelable clone = ViewNodeParcelable.CREATOR.createFromParcel(parcel); + assertThat(clone).isNotNull(); + return clone; + } finally { + parcel.recycle(); + } + } + private AssistStructure cloneThroughParcel(AssistStructure structure) { Parcel parcel = Parcel.obtain(); diff --git a/core/tests/coretests/src/android/graphics/OWNERS b/core/tests/coretests/src/android/graphics/OWNERS new file mode 100644 index 000000000000..1e8478eeb141 --- /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 82d066f6ab16..05ff21853131 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -67,6 +67,7 @@ public class TypefaceSystemFallbackTest { 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 @@ public class TypefaceSystemFallbackTest { 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 @@ public class TypefaceSystemFallbackTest { 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 @@ public class TypefaceSystemFallbackTest { 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 @@ public class TypefaceSystemFallbackTest { } 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 @@ public class TypefaceSystemFallbackTest { + "</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/android/util/TypedValueTest.kt b/core/tests/coretests/src/android/util/TypedValueTest.kt new file mode 100644 index 000000000000..7a05d970de33 --- /dev/null +++ b/core/tests/coretests/src/android/util/TypedValueTest.kt @@ -0,0 +1,155 @@ +/* + * 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.util + +import androidx.test.filters.LargeTest +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import kotlin.math.abs +import kotlin.math.min +import kotlin.math.roundToInt + +@RunWith(AndroidJUnit4::class) +class TypedValueTest { + @LargeTest + @Test + fun testFloatToComplex() { + fun assertRoundTripEquals(value: Float, expectedRadix: Int? = null) { + val complex = TypedValue.floatToComplex(value) + // Ensure values are accurate within .5% of the original value and within .5 + val delta = min(abs(value) / 512f, .5f) + assertEquals(value, TypedValue.complexToFloat(complex), delta) + // If expectedRadix is provided, validate it + if (expectedRadix != null) { + val actualRadix = ((complex shr TypedValue.COMPLEX_RADIX_SHIFT) + and TypedValue.COMPLEX_RADIX_MASK) + assertEquals("Incorrect radix for $value:", expectedRadix, actualRadix) + } + } + + assertRoundTripEquals(0f, TypedValue.COMPLEX_RADIX_23p0) + + assertRoundTripEquals(0.5f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(0.05f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(0.005f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(0.0005f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(0.00005f, TypedValue.COMPLEX_RADIX_0p23) + + assertRoundTripEquals(1.5f, TypedValue.COMPLEX_RADIX_8p15) + assertRoundTripEquals(10.5f, TypedValue.COMPLEX_RADIX_8p15) + assertRoundTripEquals(100.5f, TypedValue.COMPLEX_RADIX_8p15) + assertRoundTripEquals(255.5f, TypedValue.COMPLEX_RADIX_8p15) // 2^8 - .5 + + assertRoundTripEquals(256.5f, TypedValue.COMPLEX_RADIX_16p7) // 2^8 + .5 + assertRoundTripEquals(1000.5f, TypedValue.COMPLEX_RADIX_16p7) + assertRoundTripEquals(10000.5f, TypedValue.COMPLEX_RADIX_16p7) + assertRoundTripEquals(65535.5f, TypedValue.COMPLEX_RADIX_16p7) // 2^16 - .5 + + assertRoundTripEquals(65536.5f, TypedValue.COMPLEX_RADIX_23p0) // 2^16 + .5 + assertRoundTripEquals(100000.5f, TypedValue.COMPLEX_RADIX_23p0) + assertRoundTripEquals(1000000.5f, TypedValue.COMPLEX_RADIX_23p0) + assertRoundTripEquals(8388607.2f, TypedValue.COMPLEX_RADIX_23p0) // 2^23 -.8 + + assertRoundTripEquals(-0.5f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(-0.05f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(-0.005f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(-0.0005f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(-0.00005f, TypedValue.COMPLEX_RADIX_0p23) + + assertRoundTripEquals(-1.5f, TypedValue.COMPLEX_RADIX_8p15) + assertRoundTripEquals(-10.5f, TypedValue.COMPLEX_RADIX_8p15) + assertRoundTripEquals(-100.5f, TypedValue.COMPLEX_RADIX_8p15) + assertRoundTripEquals(-255.5f, TypedValue.COMPLEX_RADIX_8p15) // -2^8 + .5 + + // NOTE: -256.5f fits in COMPLEX_RADIX_8p15 but is stored with COMPLEX_RADIX_16p7 for + // simplicity of the algorithm. However, it's better not to enforce that with a test. + assertRoundTripEquals(-257.5f, TypedValue.COMPLEX_RADIX_16p7) // -2^8 - 1.5 + assertRoundTripEquals(-1000.5f, TypedValue.COMPLEX_RADIX_16p7) + assertRoundTripEquals(-10000.5f, TypedValue.COMPLEX_RADIX_16p7) + assertRoundTripEquals(-65535.5f, TypedValue.COMPLEX_RADIX_16p7) // -2^16 + .5 + + // NOTE: -65536.5f fits in COMPLEX_RADIX_16p7 but is stored with COMPLEX_RADIX_23p0 for + // simplicity of the algorithm. However, it's better not to enforce that with a test. + assertRoundTripEquals(-65537.5f, TypedValue.COMPLEX_RADIX_23p0) // -2^16 - 1.5 + assertRoundTripEquals(-100000.5f, TypedValue.COMPLEX_RADIX_23p0) + assertRoundTripEquals(-1000000.5f, TypedValue.COMPLEX_RADIX_23p0) + assertRoundTripEquals(-8388607.5f, TypedValue.COMPLEX_RADIX_23p0) // 2^23 -.5 + + // Test for every integer value in the range... + for (i: Int in -(1 shl 23) until (1 shl 23)) { + // ... that true integers are stored as the precise integer + assertRoundTripEquals(i.toFloat(), TypedValue.COMPLEX_RADIX_23p0) + // ... that values round up when just below an integer + assertRoundTripEquals(i - .1f) + // ... that values round down when just above an integer + assertRoundTripEquals(i + .1f) + } + } + + @SmallTest + @Test(expected = IllegalArgumentException::class) + fun testFloatToComplex_failsIfValueTooLarge() { + TypedValue.floatToComplex(8388607.5f) // 2^23 - .5 + } + + @SmallTest + @Test(expected = IllegalArgumentException::class) + fun testFloatToComplex_failsIfValueTooSmall() { + TypedValue.floatToComplex(8388608.5f) // -2^23 - .5 + } + + @LargeTest + @Test + fun testIntToComplex() { + // Validates every single valid value + for (value: Int in -(1 shl 23) until (1 shl 23)) { + assertEquals(value.toFloat(), TypedValue.complexToFloat(TypedValue.intToComplex(value))) + } + } + + @SmallTest + @Test(expected = IllegalArgumentException::class) + fun testIntToComplex_failsIfValueTooLarge() { + TypedValue.intToComplex(0x800000) + } + + @SmallTest + @Test(expected = IllegalArgumentException::class) + fun testIntToComplex_failsIfValueTooSmall() { + TypedValue.intToComplex(-0x800001) + } + + @SmallTest + @Test + fun testCreateComplexDimension_appliesUnits() { + val metrics: DisplayMetrics = mock(DisplayMetrics::class.java) + metrics.density = 3.25f + + val height = 52 * metrics.density + val widthFloat = height * 16 / 9 + val widthDimen = TypedValue.createComplexDimension( + widthFloat / metrics.density, + TypedValue.COMPLEX_UNIT_DIP + ) + val widthPx = TypedValue.complexToDimensionPixelSize(widthDimen, metrics) + assertEquals(widthFloat.roundToInt(), widthPx) + } +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java index 92fb52837c36..c636912ee98d 100644 --- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java +++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java @@ -109,11 +109,15 @@ public class EditorInfoTest { editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; final int expectedTextBeforeCursorLength = 0; final int expectedTextAfterCursorLength = testText.length(); + final SurroundingText expectedSurroundingText = + new SurroundingText(testText, editorInfo.initialSelStart, + editorInfo.initialSelEnd, 0); + editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, - expectedTextAfterCursorLength); + expectedTextAfterCursorLength, expectedSurroundingText); } @Test @@ -125,11 +129,14 @@ public class EditorInfoTest { editorInfo.initialSelEnd = testText.length(); final int expectedTextBeforeCursorLength = testText.length(); final int expectedTextAfterCursorLength = 0; + final SurroundingText expectedSurroundingText = + new SurroundingText(testText, editorInfo.initialSelStart, + editorInfo.initialSelEnd, 0); editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, - expectedTextAfterCursorLength); + expectedTextAfterCursorLength, expectedSurroundingText); } @Test @@ -141,11 +148,14 @@ public class EditorInfoTest { editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; final int expectedTextBeforeCursorLength = editorInfo.initialSelStart; final int expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd; + final SurroundingText expectedSurroundingText = + new SurroundingText(testText, editorInfo.initialSelStart, + editorInfo.initialSelEnd, 0); editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, - expectedTextAfterCursorLength); + expectedTextAfterCursorLength, expectedSurroundingText); } @Test @@ -158,11 +168,14 @@ public class EditorInfoTest { final int expectedTextBeforeCursorLength = testText.length() / 2; final int expectedTextAfterCursorLength = testText.length() - testText.length() / 2 - selectionLength; - + final SurroundingText expectedSurroundingText = + new SurroundingText(testText, + editorInfo.initialSelEnd, + editorInfo.initialSelStart , 0); editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, - expectedTextAfterCursorLength); + expectedTextAfterCursorLength, expectedSurroundingText); } @Test @@ -174,9 +187,10 @@ public class EditorInfoTest { editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, - /* expectBeforeCursorLength= */null, - /* expectSelectionLength= */null, - /* expectAfterCursorLength= */null); + /* expectBeforeCursorLength= */ null, + /* expectSelectionLength= */ null, + /* expectAfterCursorLength= */ null, + /* expectSurroundingText= */ null); } @Test @@ -190,11 +204,23 @@ public class EditorInfoTest { (int) (0.8 * (EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH - selectionLength))); final int expectedTextAfterCursorLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH - expectedTextBeforeCursorLength - selectionLength; + final int offset = editorInfo.initialSelStart - expectedTextBeforeCursorLength; + final CharSequence beforeCursor = testText.subSequence( + offset, offset + expectedTextBeforeCursorLength); + final CharSequence afterCursor = testText.subSequence(editorInfo.initialSelEnd, + editorInfo.initialSelEnd + expectedTextAfterCursorLength); + final CharSequence selectedText = testText.subSequence(editorInfo.initialSelStart, + editorInfo.initialSelEnd); + + final SurroundingText expectedSurroundingText = + new SurroundingText(TextUtils.concat(beforeCursor, selectedText, afterCursor), + expectedTextBeforeCursorLength, + expectedTextBeforeCursorLength + selectionLength, offset); editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, - expectedTextAfterCursorLength); + expectedTextAfterCursorLength, expectedSurroundingText); } @Test @@ -207,11 +233,22 @@ public class EditorInfoTest { final int expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart, (int) (0.8 * EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH)); final int expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd; + final CharSequence before = testText.subSequence( + editorInfo.initialSelStart - expectedTextBeforeCursorLength, + expectedTextBeforeCursorLength); + final CharSequence after = testText.subSequence(editorInfo.initialSelEnd, + editorInfo.initialSelEnd + expectedTextAfterCursorLength); + final SurroundingText expectedSurroundingText = + new SurroundingText(TextUtils.concat(before, after), + expectedTextBeforeCursorLength, + expectedTextBeforeCursorLength, + editorInfo.initialSelStart - expectedTextBeforeCursorLength); editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, - /* expectSelectionLength= */null, expectedTextAfterCursorLength); + /* expectSelectionLength= */null, expectedTextAfterCursorLength, + expectedSurroundingText); } @Test @@ -269,6 +306,19 @@ public class EditorInfoTest { InputConnection.GET_TEXT_WITH_STYLES), targetEditorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH, InputConnection.GET_TEXT_WITH_STYLES))); + + final SurroundingText sourceSurroundingText = sourceEditorInfo.getInitialSurroundingText( + LONG_EXP_TEXT_LENGTH, LONG_EXP_TEXT_LENGTH, InputConnection.GET_TEXT_WITH_STYLES); + final SurroundingText targetSurroundingText = targetEditorInfo.getInitialSurroundingText( + LONG_EXP_TEXT_LENGTH, LONG_EXP_TEXT_LENGTH, InputConnection.GET_TEXT_WITH_STYLES); + + assertTrue(TextUtils.equals(sourceSurroundingText.getText(), + targetSurroundingText.getText())); + assertEquals(sourceSurroundingText.getSelectionStart(), + targetSurroundingText.getSelectionStart()); + assertEquals(sourceSurroundingText.getSelectionEnd(), + targetSurroundingText.getSelectionEnd()); + assertEquals(sourceSurroundingText.getOffset(), targetSurroundingText.getOffset()); } @Test @@ -338,7 +388,8 @@ public class EditorInfoTest { private static void assertExpectedTextLength(EditorInfo editorInfo, @Nullable Integer expectBeforeCursorLength, @Nullable Integer expectSelectionLength, - @Nullable Integer expectAfterCursorLength) { + @Nullable Integer expectAfterCursorLength, + @Nullable SurroundingText expectSurroundingText) { final CharSequence textBeforeCursor = editorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH, InputConnection.GET_TEXT_WITH_STYLES); @@ -347,6 +398,10 @@ public class EditorInfoTest { final CharSequence textAfterCursor = editorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH, InputConnection.GET_TEXT_WITH_STYLES); + final SurroundingText surroundingText = editorInfo.getInitialSurroundingText( + LONG_EXP_TEXT_LENGTH, + LONG_EXP_TEXT_LENGTH, + InputConnection.GET_TEXT_WITH_STYLES); if (expectBeforeCursorLength == null) { assertNull(textBeforeCursor); @@ -365,6 +420,18 @@ public class EditorInfoTest { } else { assertEquals(expectAfterCursorLength.intValue(), textAfterCursor.length()); } + + if (expectSurroundingText == null) { + assertNull(surroundingText); + } else { + assertTrue(TextUtils.equals( + expectSurroundingText.getText(), surroundingText.getText())); + assertEquals(expectSurroundingText.getSelectionStart(), + surroundingText.getSelectionStart()); + assertEquals(expectSurroundingText.getSelectionEnd(), + surroundingText.getSelectionEnd()); + assertEquals(expectSurroundingText.getOffset(), surroundingText.getOffset()); + } } private static CharSequence createTestText(int surroundingLength) { diff --git a/core/tests/coretests/src/android/widget/TextViewOnReceiveContentTest.java b/core/tests/coretests/src/android/widget/TextViewOnReceiveContentTest.java index 9978648ee32e..9968f7975156 100644 --- a/core/tests/coretests/src/android/widget/TextViewOnReceiveContentTest.java +++ b/core/tests/coretests/src/android/widget/TextViewOnReceiveContentTest.java @@ -87,7 +87,7 @@ public class TextViewOnReceiveContentTest { } @Test - public void testGetEditorInfoMimeTypes_fallbackToCommitContent() throws Throwable { + public void testGetFallbackMimeTypesForAutofill() throws Throwable { // Configure the EditText with an EditorInfo/InputConnection that supports some image MIME // types. String[] mimeTypes = {"image/gif", "image/png"}; @@ -99,11 +99,12 @@ public class TextViewOnReceiveContentTest { onView(withId(mEditText.getId())).perform(clickOnTextAtIndex(0)); // Assert that the default listener returns the MIME types declared in the EditorInfo. - assertThat(mDefaultReceiver.getEditorInfoMimeTypes(mEditText)).isEqualTo(mimeTypes); + assertThat(mDefaultReceiver.getFallbackMimeTypesForAutofill(mEditText)).isEqualTo( + mimeTypes); } @Test - public void testGetEditorInfoMimeTypes_fallbackToCommitContent_noMimeTypesInEditorInfo() + public void testGetFallbackMimeTypesForAutofill_noMimeTypesInEditorInfo() throws Throwable { // Configure the EditText with an EditorInfo/InputConnection that doesn't declare any MIME // types. @@ -115,7 +116,7 @@ public class TextViewOnReceiveContentTest { onView(withId(mEditText.getId())).perform(clickOnTextAtIndex(0)); // Assert that the default listener returns null as the MIME types. - assertThat(mDefaultReceiver.getEditorInfoMimeTypes(mEditText)).isNull(); + assertThat(mDefaultReceiver.getFallbackMimeTypesForAutofill(mEditText)).isNull(); } @Test diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java index 66fdfff990f8..a4284a04310e 100644 --- a/core/tests/coretests/src/android/widget/TextViewTest.java +++ b/core/tests/coretests/src/android/widget/TextViewTest.java @@ -276,6 +276,34 @@ public class TextViewTest { 0, mTextView.getImeOptions() & EditorInfo.IME_FLAG_NO_FULLSCREEN); } + @Test + @UiThreadTest + public void setSetImeTemporarilyConsumesInput_recoveryToVisible() { + mTextView = new TextView(mActivity); + mTextView.setCursorVisible(true); + assertTrue(mTextView.isCursorVisible()); + + mTextView.setImeTemporarilyConsumesInput(true); + assertFalse(mTextView.isCursorVisible()); + + mTextView.setImeTemporarilyConsumesInput(false); + assertTrue(mTextView.isCursorVisible()); + } + + @Test + @UiThreadTest + public void setSetImeTemporarilyConsumesInput_recoveryToInvisible() { + mTextView = new TextView(mActivity); + mTextView.setCursorVisible(false); + assertFalse(mTextView.isCursorVisible()); + + mTextView.setImeTemporarilyConsumesInput(true); + assertFalse(mTextView.isCursorVisible()); + + mTextView.setImeTemporarilyConsumesInput(false); + assertFalse(mTextView.isCursorVisible()); + } + private String createLongText() { int size = 600 * 1000; final StringBuilder builder = new StringBuilder(size); 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 000000000000..e2a106484848 --- /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 000000000000..ed4638c1e7e0 --- /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 bd4154210cbf..8ff318e8d555 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 @@ import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ + AmbientDisplayPowerCalculatorTest.class, + AudioPowerCalculatorTest.class, BatteryStatsCpuTimesTest.class, BatteryStatsBackgroundStatsTest.class, BatteryStatsBinderCallStatsTest.class, @@ -42,6 +44,9 @@ import org.junit.runners.Suite; BatteryStatsUserLifecycleTests.class, BluetoothPowerCalculatorTest.class, BstatsCpuTimesValidationTest.class, + CameraPowerCalculatorTest.class, + FlashlightPowerCalculatorTest.class, + IdlePowerCalculatorTest.class, KernelCpuProcStringReaderTest.class, KernelCpuUidActiveTimeReaderTest.class, KernelCpuUidBpfMapReaderTest.class, @@ -55,6 +60,9 @@ import org.junit.runners.Suite; 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 000000000000..55f64f977933 --- /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 96eb8da71ddb..e5594712db10 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 @@ package com.android.internal.os; 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 @@ public class BluetoothPowerCalculatorTest { 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 @@ public class BluetoothPowerCalculatorTest { 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 000000000000..a21dd58b0757 --- /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 000000000000..b7bbedd9cdb7 --- /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 000000000000..781e72560279 --- /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 000000000000..8f21503a6d77 --- /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 c7751371d557..fc237219be51 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 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { 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 000000000000..e43caa37f711 --- /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 dbb36fbd1650..a5cafb974b0e 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 @@ public class SystemServicePowerCalculatorTest { 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()); + + mStatsRule.apply(calculator); - assertEquals(0.00016269, app1.systemServiceCpuPowerMah, 0.0000001); - assertEquals(0.00146426, app2.systemServiceCpuPowerMah, 0.0000001); + 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 000000000000..39eac49400ec --- /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/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java b/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java index 6df1c3ed220f..c01bb75c32aa 100644 --- a/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java +++ b/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java @@ -45,14 +45,14 @@ public class RangingManagerTest { private static final IUwbAdapter ADAPTER = mock(IUwbAdapter.class); private static final Executor EXECUTOR = UwbTestUtils.getExecutor(); private static final PersistableBundle PARAMS = new PersistableBundle(); - private static final @CloseReason int CLOSE_REASON = CloseReason.UNKNOWN; + private static final @RangingChangeReason int REASON = RangingChangeReason.UNKNOWN; @Test - public void testOpenSession_StartRangingInvoked() throws RemoteException { + public void testOpenSession_OpenRangingInvoked() throws RemoteException { RangingManager rangingManager = new RangingManager(ADAPTER); RangingSession.Callback callback = mock(RangingSession.Callback.class); rangingManager.openSession(PARAMS, EXECUTOR, callback); - verify(ADAPTER, times(1)).startRanging(eq(rangingManager), eq(PARAMS)); + verify(ADAPTER, times(1)).openRanging(eq(rangingManager), eq(PARAMS)); } @Test @@ -60,7 +60,7 @@ public class RangingManagerTest { RangingManager rangingManager = new RangingManager(ADAPTER); RangingSession.Callback callback = mock(RangingSession.Callback.class); SessionHandle handle = new SessionHandle(1); - when(ADAPTER.startRanging(any(), any())).thenReturn(handle); + when(ADAPTER.openRanging(any(), any())).thenReturn(handle); rangingManager.openSession(PARAMS, EXECUTOR, callback); @@ -73,34 +73,34 @@ public class RangingManagerTest { } @Test - public void testOnRangingStarted_ValidSessionHandle() throws RemoteException { + public void testOnRangingOpened_ValidSessionHandle() throws RemoteException { RangingManager rangingManager = new RangingManager(ADAPTER); RangingSession.Callback callback = mock(RangingSession.Callback.class); SessionHandle handle = new SessionHandle(1); - when(ADAPTER.startRanging(any(), any())).thenReturn(handle); + when(ADAPTER.openRanging(any(), any())).thenReturn(handle); rangingManager.openSession(PARAMS, EXECUTOR, callback); - rangingManager.onRangingStarted(handle, PARAMS); - verify(callback, times(1)).onOpenSuccess(any(), any()); + rangingManager.onRangingOpened(handle); + verify(callback, times(1)).onOpened(any()); } @Test - public void testOnRangingStarted_InvalidSessionHandle() throws RemoteException { + public void testOnRangingOpened_InvalidSessionHandle() throws RemoteException { RangingManager rangingManager = new RangingManager(ADAPTER); RangingSession.Callback callback = mock(RangingSession.Callback.class); - rangingManager.onRangingStarted(new SessionHandle(2), PARAMS); - verify(callback, times(0)).onOpenSuccess(any(), any()); + rangingManager.onRangingOpened(new SessionHandle(2)); + verify(callback, times(0)).onOpened(any()); } @Test - public void testOnRangingStarted_MultipleSessionsRegistered() throws RemoteException { + public void testOnRangingOpened_MultipleSessionsRegistered() throws RemoteException { SessionHandle sessionHandle1 = new SessionHandle(1); SessionHandle sessionHandle2 = new SessionHandle(2); RangingSession.Callback callback1 = mock(RangingSession.Callback.class); RangingSession.Callback callback2 = mock(RangingSession.Callback.class); - when(ADAPTER.startRanging(any(), any())) + when(ADAPTER.openRanging(any(), any())) .thenReturn(sessionHandle1) .thenReturn(sessionHandle2); @@ -108,25 +108,50 @@ public class RangingManagerTest { rangingManager.openSession(PARAMS, EXECUTOR, callback1); rangingManager.openSession(PARAMS, EXECUTOR, callback2); - rangingManager.onRangingStarted(sessionHandle1, PARAMS); - verify(callback1, times(1)).onOpenSuccess(any(), any()); - verify(callback2, times(0)).onOpenSuccess(any(), any()); + rangingManager.onRangingOpened(sessionHandle1); + verify(callback1, times(1)).onOpened(any()); + verify(callback2, times(0)).onOpened(any()); - rangingManager.onRangingStarted(sessionHandle2, PARAMS); - verify(callback1, times(1)).onOpenSuccess(any(), any()); - verify(callback2, times(1)).onOpenSuccess(any(), any()); + rangingManager.onRangingOpened(sessionHandle2); + verify(callback1, times(1)).onOpened(any()); + verify(callback2, times(1)).onOpened(any()); } @Test - public void testOnRangingClosed_OnRangingClosedCalled() throws RemoteException { + public void testCorrectCallbackInvoked() throws RemoteException { RangingManager rangingManager = new RangingManager(ADAPTER); RangingSession.Callback callback = mock(RangingSession.Callback.class); SessionHandle handle = new SessionHandle(1); - when(ADAPTER.startRanging(any(), any())).thenReturn(handle); + when(ADAPTER.openRanging(any(), any())).thenReturn(handle); + rangingManager.openSession(PARAMS, EXECUTOR, callback); + rangingManager.onRangingOpened(handle); + verify(callback, times(1)).onOpened(any()); + + rangingManager.onRangingStarted(handle, PARAMS); + verify(callback, times(1)).onStarted(eq(PARAMS)); + + rangingManager.onRangingStartFailed(handle, REASON, PARAMS); + verify(callback, times(1)).onStartFailed(eq(REASON), eq(PARAMS)); + + RangingReport report = UwbTestUtils.getRangingReports(1); + rangingManager.onRangingResult(handle, report); + verify(callback, times(1)).onReportReceived(eq(report)); - rangingManager.onRangingClosed(handle, CLOSE_REASON, PARAMS); - verify(callback, times(1)).onClosed(anyInt(), any()); + rangingManager.onRangingReconfigured(handle, PARAMS); + verify(callback, times(1)).onReconfigured(eq(PARAMS)); + + rangingManager.onRangingReconfigureFailed(handle, REASON, PARAMS); + verify(callback, times(1)).onReconfigureFailed(eq(REASON), eq(PARAMS)); + + rangingManager.onRangingStopped(handle); + verify(callback, times(1)).onStopped(); + + rangingManager.onRangingStopFailed(handle, REASON, PARAMS); + verify(callback, times(1)).onStopFailed(eq(REASON), eq(PARAMS)); + + rangingManager.onRangingClosed(handle, REASON, PARAMS); + verify(callback, times(1)).onClosed(eq(REASON), eq(PARAMS)); } @Test @@ -138,7 +163,7 @@ public class RangingManagerTest { RangingSession.Callback callback1 = mock(RangingSession.Callback.class); RangingSession.Callback callback2 = mock(RangingSession.Callback.class); - when(ADAPTER.startRanging(any(), any())) + when(ADAPTER.openRanging(any(), any())) .thenReturn(sessionHandle1) .thenReturn(sessionHandle2); @@ -146,37 +171,23 @@ public class RangingManagerTest { rangingManager.openSession(PARAMS, EXECUTOR, callback1); rangingManager.openSession(PARAMS, EXECUTOR, callback2); - rangingManager.onRangingClosed(sessionHandle1, CLOSE_REASON, PARAMS); + rangingManager.onRangingClosed(sessionHandle1, REASON, PARAMS); verify(callback1, times(1)).onClosed(anyInt(), any()); verify(callback2, times(0)).onClosed(anyInt(), any()); - rangingManager.onRangingClosed(sessionHandle2, CLOSE_REASON, PARAMS); + rangingManager.onRangingClosed(sessionHandle2, REASON, PARAMS); verify(callback1, times(1)).onClosed(anyInt(), any()); verify(callback2, times(1)).onClosed(anyInt(), any()); } @Test - public void testOnRangingReport_OnReportReceived() throws RemoteException { - RangingManager rangingManager = new RangingManager(ADAPTER); - RangingSession.Callback callback = mock(RangingSession.Callback.class); - SessionHandle handle = new SessionHandle(1); - when(ADAPTER.startRanging(any(), any())).thenReturn(handle); - rangingManager.openSession(PARAMS, EXECUTOR, callback); - rangingManager.onRangingStarted(handle, PARAMS); - - RangingReport report = UwbTestUtils.getRangingReports(1); - rangingManager.onRangingResult(handle, report); - verify(callback, times(1)).onReportReceived(eq(report)); - } - - @Test public void testOnRangingReport_MultipleSessionsRegistered() throws RemoteException { SessionHandle sessionHandle1 = new SessionHandle(1); SessionHandle sessionHandle2 = new SessionHandle(2); RangingSession.Callback callback1 = mock(RangingSession.Callback.class); RangingSession.Callback callback2 = mock(RangingSession.Callback.class); - when(ADAPTER.startRanging(any(), any())) + when(ADAPTER.openRanging(any(), any())) .thenReturn(sessionHandle1) .thenReturn(sessionHandle2); @@ -196,65 +207,54 @@ public class RangingManagerTest { } @Test - public void testOnClose_Reasons() throws RemoteException { - runOnClose_Reason(CloseReason.LOCAL_API, - RangingSession.Callback.CLOSE_REASON_LOCAL_CLOSE_API); + public void testReasons() throws RemoteException { + runReason(RangingChangeReason.LOCAL_API, + RangingSession.Callback.REASON_LOCAL_REQUEST); + + runReason(RangingChangeReason.MAX_SESSIONS_REACHED, + RangingSession.Callback.REASON_MAX_SESSIONS_REACHED); - runOnClose_Reason(CloseReason.MAX_SESSIONS_REACHED, - RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED); + runReason(RangingChangeReason.PROTOCOL_SPECIFIC, + RangingSession.Callback.REASON_PROTOCOL_SPECIFIC_ERROR); - runOnClose_Reason(CloseReason.PROTOCOL_SPECIFIC, - RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC); + runReason(RangingChangeReason.REMOTE_REQUEST, + RangingSession.Callback.REASON_REMOTE_REQUEST); - runOnClose_Reason(CloseReason.REMOTE_REQUEST, - RangingSession.Callback.CLOSE_REASON_REMOTE_REQUEST); + runReason(RangingChangeReason.SYSTEM_POLICY, + RangingSession.Callback.REASON_SYSTEM_POLICY); - runOnClose_Reason(CloseReason.SYSTEM_POLICY, - RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY); + runReason(RangingChangeReason.BAD_PARAMETERS, + RangingSession.Callback.REASON_BAD_PARAMETERS); - runOnClose_Reason(CloseReason.UNKNOWN, - RangingSession.Callback.CLOSE_REASON_UNKNOWN); + runReason(RangingChangeReason.UNKNOWN, + RangingSession.Callback.REASON_UNKNOWN); } - private void runOnClose_Reason(@CloseReason int reasonIn, - @RangingSession.Callback.CloseReason int reasonOut) throws RemoteException { + private void runReason(@RangingChangeReason int reasonIn, + @RangingSession.Callback.Reason int reasonOut) throws RemoteException { RangingManager rangingManager = new RangingManager(ADAPTER); RangingSession.Callback callback = mock(RangingSession.Callback.class); SessionHandle handle = new SessionHandle(1); - when(ADAPTER.startRanging(any(), any())).thenReturn(handle); + when(ADAPTER.openRanging(any(), any())).thenReturn(handle); rangingManager.openSession(PARAMS, EXECUTOR, callback); - rangingManager.onRangingClosed(handle, reasonIn, PARAMS); - verify(callback, times(1)).onClosed(eq(reasonOut), eq(PARAMS)); - } - - @Test - public void testStartFailureReasons() throws RemoteException { - runOnRangingStartFailed_Reason(StartFailureReason.BAD_PARAMETERS, - RangingSession.Callback.CLOSE_REASON_LOCAL_BAD_PARAMETERS); + rangingManager.onRangingOpenFailed(handle, reasonIn, PARAMS); + verify(callback, times(1)).onOpenFailed(eq(reasonOut), eq(PARAMS)); - runOnRangingStartFailed_Reason(StartFailureReason.MAX_SESSIONS_REACHED, - RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED); + // Open a new session + rangingManager.openSession(PARAMS, EXECUTOR, callback); + rangingManager.onRangingOpened(handle); - runOnRangingStartFailed_Reason(StartFailureReason.PROTOCOL_SPECIFIC, - RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC); + rangingManager.onRangingStartFailed(handle, reasonIn, PARAMS); + verify(callback, times(1)).onStartFailed(eq(reasonOut), eq(PARAMS)); - runOnRangingStartFailed_Reason(StartFailureReason.SYSTEM_POLICY, - RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY); + rangingManager.onRangingReconfigureFailed(handle, reasonIn, PARAMS); + verify(callback, times(1)).onReconfigureFailed(eq(reasonOut), eq(PARAMS)); - runOnRangingStartFailed_Reason(StartFailureReason.UNKNOWN, - RangingSession.Callback.CLOSE_REASON_UNKNOWN); - } - - private void runOnRangingStartFailed_Reason(@StartFailureReason int reasonIn, - @RangingSession.Callback.CloseReason int reasonOut) throws RemoteException { - RangingManager rangingManager = new RangingManager(ADAPTER); - RangingSession.Callback callback = mock(RangingSession.Callback.class); - SessionHandle handle = new SessionHandle(1); - when(ADAPTER.startRanging(any(), any())).thenReturn(handle); - rangingManager.openSession(PARAMS, EXECUTOR, callback); + rangingManager.onRangingStopFailed(handle, reasonIn, PARAMS); + verify(callback, times(1)).onStopFailed(eq(reasonOut), eq(PARAMS)); - rangingManager.onRangingStartFailed(handle, reasonIn, PARAMS); + rangingManager.onRangingClosed(handle, reasonIn, PARAMS); verify(callback, times(1)).onClosed(eq(reasonOut), eq(PARAMS)); } } diff --git a/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java b/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java index 702c68ebc9de..e5eea26f5d11 100644 --- a/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java +++ b/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java @@ -19,9 +19,11 @@ package android.uwb; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -34,6 +36,8 @@ import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.util.concurrent.Executor; @@ -43,47 +47,48 @@ import java.util.concurrent.Executor; @SmallTest @RunWith(AndroidJUnit4.class) public class RangingSessionTest { - private static final IUwbAdapter ADAPTER = mock(IUwbAdapter.class); private static final Executor EXECUTOR = UwbTestUtils.getExecutor(); private static final PersistableBundle PARAMS = new PersistableBundle(); - private static final @RangingSession.Callback.CloseReason int CLOSE_REASON = - RangingSession.Callback.CLOSE_REASON_LOCAL_GENERIC_ERROR; + private static final @RangingSession.Callback.Reason int REASON = + RangingSession.Callback.REASON_GENERIC_ERROR; @Test - public void testOnRangingStarted_OnOpenSuccessCalled() { + public void testOnRangingOpened_OnOpenSuccessCalled() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); verifyOpenState(session, false); - session.onRangingStarted(PARAMS); + session.onRangingOpened(); verifyOpenState(session, true); // Verify that the onOpenSuccess callback was invoked - verify(callback, times(1)).onOpenSuccess(eq(session), any()); + verify(callback, times(1)).onOpened(eq(session)); verify(callback, times(0)).onClosed(anyInt(), any()); } @Test - public void testOnRangingStarted_CannotOpenClosedSession() { + public void testOnRangingOpened_CannotOpenClosedSession() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); - session.onRangingStarted(PARAMS); + session.onRangingOpened(); verifyOpenState(session, true); - verify(callback, times(1)).onOpenSuccess(eq(session), any()); + verify(callback, times(1)).onOpened(eq(session)); verify(callback, times(0)).onClosed(anyInt(), any()); - session.onRangingClosed(CLOSE_REASON, PARAMS); + session.onRangingClosed(REASON, PARAMS); verifyOpenState(session, false); - verify(callback, times(1)).onOpenSuccess(eq(session), any()); + verify(callback, times(1)).onOpened(eq(session)); verify(callback, times(1)).onClosed(anyInt(), any()); // Now invoke the ranging started callback and ensure the session remains closed - session.onRangingStarted(PARAMS); + session.onRangingOpened(); verifyOpenState(session, false); - verify(callback, times(1)).onOpenSuccess(eq(session), any()); + verify(callback, times(1)).onOpened(eq(session)); verify(callback, times(1)).onClosed(anyInt(), any()); } @@ -91,27 +96,30 @@ public class RangingSessionTest { public void testOnRangingClosed_OnClosedCalledWhenSessionNotOpen() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); verifyOpenState(session, false); - session.onRangingClosed(CLOSE_REASON, PARAMS); + session.onRangingClosed(REASON, PARAMS); verifyOpenState(session, false); // Verify that the onOpenSuccess callback was invoked - verify(callback, times(0)).onOpenSuccess(eq(session), any()); + verify(callback, times(0)).onOpened(eq(session)); verify(callback, times(1)).onClosed(anyInt(), any()); } - @Test public void testOnRangingClosed_OnClosedCalled() { + @Test + public void testOnRangingClosed_OnClosedCalled() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); session.onRangingStarted(PARAMS); - session.onRangingClosed(CLOSE_REASON, PARAMS); + session.onRangingClosed(REASON, PARAMS); verify(callback, times(1)).onClosed(anyInt(), any()); verifyOpenState(session, false); - session.onRangingClosed(CLOSE_REASON, PARAMS); + session.onRangingClosed(REASON, PARAMS); verify(callback, times(2)).onClosed(anyInt(), any()); } @@ -119,7 +127,8 @@ public class RangingSessionTest { public void testOnRangingResult_OnReportReceivedCalled() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); verifyOpenState(session, false); session.onRangingStarted(PARAMS); @@ -131,11 +140,83 @@ public class RangingSessionTest { } @Test - public void testClose() throws RemoteException { + public void testStart_CannotStartIfAlreadyStarted() throws RemoteException { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any()); + session.onRangingOpened(); + + session.start(PARAMS); + verify(callback, times(1)).onStarted(any()); + + // Calling start again should throw an illegal state + verifyThrowIllegalState(() -> session.start(PARAMS)); + verify(callback, times(1)).onStarted(any()); + } + + @Test + public void testStop_CannotStopIfAlreadyStopped() throws RemoteException { + SessionHandle handle = new SessionHandle(123); + RangingSession.Callback callback = mock(RangingSession.Callback.class); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any()); + doAnswer(new StopAnswer(session)).when(adapter).stopRanging(any()); + session.onRangingOpened(); + session.start(PARAMS); + + verifyNoThrowIllegalState(session::stop); + verify(callback, times(1)).onStopped(); + + // Calling stop again should throw an illegal state + verifyThrowIllegalState(session::stop); + verify(callback, times(1)).onStopped(); + } + + @Test + public void testReconfigure_OnlyWhenOpened() throws RemoteException { + SessionHandle handle = new SessionHandle(123); + RangingSession.Callback callback = mock(RangingSession.Callback.class); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any()); + doAnswer(new ReconfigureAnswer(session)).when(adapter).reconfigureRanging(any(), any()); + + verifyThrowIllegalState(() -> session.reconfigure(PARAMS)); + verify(callback, times(0)).onReconfigured(any()); + verifyOpenState(session, false); + + session.onRangingOpened(); + verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS)); + verify(callback, times(1)).onReconfigured(any()); + verifyOpenState(session, true); + session.onRangingStarted(PARAMS); + verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS)); + verify(callback, times(2)).onReconfigured(any()); + verifyOpenState(session, true); + + session.onRangingStopped(); + verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS)); + verify(callback, times(3)).onReconfigured(any()); + verifyOpenState(session, true); + + + session.onRangingClosed(REASON, PARAMS); + verifyThrowIllegalState(() -> session.reconfigure(PARAMS)); + verify(callback, times(3)).onReconfigured(any()); + verifyOpenState(session, false); + } + + @Test + public void testClose_NoCallbackUntilInvoked() throws RemoteException { + SessionHandle handle = new SessionHandle(123); + RangingSession.Callback callback = mock(RangingSession.Callback.class); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + session.onRangingOpened(); // Calling close multiple times should invoke closeRanging until the session receives // the onClosed callback. @@ -143,7 +224,7 @@ public class RangingSessionTest { for (int i = 1; i <= totalCallsBeforeOnRangingClosed; i++) { session.close(); verifyOpenState(session, true); - verify(ADAPTER, times(i)).closeRanging(handle); + verify(adapter, times(i)).closeRanging(handle); verify(callback, times(0)).onClosed(anyInt(), any()); } @@ -151,18 +232,47 @@ public class RangingSessionTest { // the session's close. final int totalCallsAfterOnRangingClosed = 2; for (int i = 1; i <= totalCallsAfterOnRangingClosed; i++) { - session.onRangingClosed(CLOSE_REASON, PARAMS); + session.onRangingClosed(REASON, PARAMS); verifyOpenState(session, false); - verify(ADAPTER, times(totalCallsBeforeOnRangingClosed)).closeRanging(handle); + verify(adapter, times(totalCallsBeforeOnRangingClosed)).closeRanging(handle); verify(callback, times(i)).onClosed(anyInt(), any()); } } @Test + public void testClose_OnClosedCalled() throws RemoteException { + SessionHandle handle = new SessionHandle(123); + RangingSession.Callback callback = mock(RangingSession.Callback.class); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any()); + session.onRangingOpened(); + + session.close(); + verify(callback, times(1)).onClosed(anyInt(), any()); + } + + @Test + public void testClose_CannotInteractFurther() throws RemoteException { + SessionHandle handle = new SessionHandle(123); + RangingSession.Callback callback = mock(RangingSession.Callback.class); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any()); + session.close(); + + verifyThrowIllegalState(() -> session.start(PARAMS)); + verifyThrowIllegalState(() -> session.reconfigure(PARAMS)); + verifyThrowIllegalState(() -> session.stop()); + verifyNoThrowIllegalState(() -> session.close()); + } + + @Test public void testOnRangingResult_OnReportReceivedCalledWhenOpen() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); assertFalse(session.isOpen()); session.onRangingStarted(PARAMS); @@ -178,7 +288,8 @@ public class RangingSessionTest { public void testOnRangingResult_OnReportReceivedNotCalledWhenNotOpen() { SessionHandle handle = new SessionHandle(123); RangingSession.Callback callback = mock(RangingSession.Callback.class); - RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); assertFalse(session.isOpen()); @@ -191,4 +302,77 @@ public class RangingSessionTest { private void verifyOpenState(RangingSession session, boolean expected) { assertEquals(expected, session.isOpen()); } + + private void verifyThrowIllegalState(Runnable runnable) { + try { + runnable.run(); + fail(); + } catch (IllegalStateException e) { + // Pass + } + } + + private void verifyNoThrowIllegalState(Runnable runnable) { + try { + runnable.run(); + } catch (IllegalStateException e) { + fail(); + } + } + + abstract class AdapterAnswer implements Answer { + protected RangingSession mSession; + + protected AdapterAnswer(RangingSession session) { + mSession = session; + } + } + + class StartAnswer extends AdapterAnswer { + StartAnswer(RangingSession session) { + super(session); + } + + @Override + public Object answer(InvocationOnMock invocation) { + mSession.onRangingStarted(PARAMS); + return null; + } + } + + class ReconfigureAnswer extends AdapterAnswer { + ReconfigureAnswer(RangingSession session) { + super(session); + } + + @Override + public Object answer(InvocationOnMock invocation) { + mSession.onRangingReconfigured(PARAMS); + return null; + } + } + + class StopAnswer extends AdapterAnswer { + StopAnswer(RangingSession session) { + super(session); + } + + @Override + public Object answer(InvocationOnMock invocation) { + mSession.onRangingStopped(); + return null; + } + } + + class CloseAnswer extends AdapterAnswer { + CloseAnswer(RangingSession session) { + super(session); + } + + @Override + public Object answer(InvocationOnMock invocation) { + mSession.onRangingClosed(REASON, PARAMS); + return null; + } + } } diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt index d8af726ffa72..52ee63a15a63 100644 --- a/framework-jarjar-rules.txt +++ b/framework-jarjar-rules.txt @@ -1,2 +1,3 @@ rule android.hidl.** android.internal.hidl.@1 rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1 +rule com.android.server.vcn.util.** com.android.server.vcn.repackaged.util.@1 diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 0782f8dfd9d3..73fff7207c45 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 android.util.Xml; 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 @@ public class FontListParser { /* 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 @@ public class FontListParser { 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 @@ public class FontListParser { /** * 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 @@ public class FontListParser { 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 @@ public class FontListParser { 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 @@ public class FontListParser { } } 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/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java index 9f46ceb61332..49888fd5f977 100644 --- a/graphics/java/android/graphics/RecordingCanvas.java +++ b/graphics/java/android/graphics/RecordingCanvas.java @@ -204,6 +204,26 @@ public final class RecordingCanvas extends BaseRecordingCanvas { } /** + * Draws a ripple + * + * @param cx + * @param cy + * @param radius + * @param paint + * @param progress + * @param shader + * + * @hide + */ + public void drawRipple(CanvasProperty<Float> cx, CanvasProperty<Float> cy, + CanvasProperty<Float> radius, CanvasProperty<Paint> paint, + CanvasProperty<Float> progress, RuntimeShader shader) { + nDrawRipple(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(), + radius.getNativeContainer(), paint.getNativeContainer(), + progress.getNativeContainer(), shader.getNativeShaderFactory()); + } + + /** * Draws a round rect * * @param left @@ -260,6 +280,9 @@ public final class RecordingCanvas extends BaseRecordingCanvas { private static native void nDrawCircle(long renderer, long propCx, long propCy, long propRadius, long propPaint); @CriticalNative + private static native void nDrawRipple(long renderer, long propCx, long propCy, long propRadius, + long propPaint, long propProgress, long runtimeEffect); + @CriticalNative private static native void nDrawRoundRect(long renderer, long propLeft, long propTop, long propRight, long propBottom, long propRx, long propRy, long propPaint); @CriticalNative diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java index fb0983a83f9d..7f2e503ac8fd 100644 --- a/graphics/java/android/graphics/RuntimeShader.java +++ b/graphics/java/android/graphics/RuntimeShader.java @@ -115,6 +115,10 @@ public class RuntimeShader extends Shader { nativeShaders, colorSpace().getNativeInstance(), mIsOpaque); } + public long getNativeShaderFactory() { + return mNativeInstanceRuntimeShaderFactory; + } + private static native long nativeCreate(long shaderFactory, long matrix, byte[] inputs, long[] shaderInputs, long colorSpaceHandle, boolean isOpaque); diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java index 0291d7484dc5..f95da82ee07c 100644 --- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -97,7 +97,7 @@ public class FontCustomizationParser { 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 fb6ea99be7ab..16a53c25db08 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -91,7 +91,9 @@ public final class SystemFonts { 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,11 +228,26 @@ public final class SystemFonts { } /** + * @see #buildSystemFallback(String, String, String, FontCustomizationParser.Result, Map) + * @hide + */ + @VisibleForTesting + public static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath, + @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 @@ -238,11 +255,12 @@ public final class SystemFonts { @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 @@ public final class SystemFonts { /** @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 6eefeb8f3f2a..e56bd5167906 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 java.util.Collection; 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 @@ class Util { * 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 @@ class Util { } } + private Util() {} } diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_controls.xml b/libs/WindowManager/Shell/res/layout/tv_pip_controls.xml deleted file mode 100644 index 9157f63ce1b3..000000000000 --- a/libs/WindowManager/Shell/res/layout/tv_pip_controls.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?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. ---> -<!-- Layout for {@link com.android.wm.shell.pip.tv.PipControlsView}. --> -<merge xmlns:android="http://schemas.android.com/apk/res/android"> - <com.android.wm.shell.pip.tv.PipControlButtonView - android:id="@+id/full_button" - android:layout_width="@dimen/picture_in_picture_button_width" - android:layout_height="wrap_content" - android:src="@drawable/pip_ic_fullscreen_white" - android:text="@string/pip_fullscreen" /> - - <com.android.wm.shell.pip.tv.PipControlButtonView - android:id="@+id/close_button" - android:layout_width="@dimen/picture_in_picture_button_width" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" - android:src="@drawable/pip_ic_close_white" - android:text="@string/pip_close" /> -</merge> diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml index 0d684e8b0ab5..49e2379589a4 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml @@ -14,19 +14,39 @@ See the License for the specific language governing permissions and limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/tv_pip_menu" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal" - android:paddingTop="350dp" - android:background="#CC000000" - android:gravity="top|center_horizontal" - android:clipChildren="false"> - - <com.android.wm.shell.pip.tv.PipControlsView - android:id="@+id/pip_controls" +<!-- Layout for TvPipMenuView --> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tv_pip_menu" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#CC000000"> + + <LinearLayout + android:id="@+id/tv_pip_menu_action_buttons" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:alpha="0" /> -</LinearLayout> + android:layout_gravity="center_horizontal" + android:layout_marginTop="350dp" + android:orientation="horizontal" + android:alpha="0"> + + <com.android.wm.shell.pip.tv.TvPipMenuActionButton + android:id="@+id/tv_pip_menu_fullscreen_button" + android:layout_width="@dimen/picture_in_picture_button_width" + android:layout_height="wrap_content" + android:src="@drawable/pip_ic_fullscreen_white" + android:text="@string/pip_fullscreen" /> + + <com.android.wm.shell.pip.tv.TvPipMenuActionButton + android:id="@+id/tv_pip_menu_close_button" + android:layout_width="@dimen/picture_in_picture_button_width" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" + android:src="@drawable/pip_ic_close_white" + android:text="@string/pip_close" /> + + <!-- More TvPipMenuActionButtons may be added here at runtime. --> + + </LinearLayout> + +</FrameLayout> diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_control_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml index 727ac3412a25..5925008e0d08 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_control_button.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -<!-- Layout for {@link com.android.wm.shell.pip.tv.PipControlButtonView}. --> +<!-- Layout for TvPipMenuActionButton --> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:id="@+id/button" diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_custom_control.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_additional_action_button.xml index 452f2cd5ccb6..bf4eb2691ff0 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_custom_control.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu_additional_action_button.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -<com.android.wm.shell.pip.tv.PipControlButtonView +<com.android.wm.shell.pip.tv.TvPipMenuActionButton xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="@dimen/picture_in_picture_button_width" android:layout_height="wrap_content" diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json index 4070829fcfbe..0290d9f4b316 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/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 807e5afae890..13f1fddfdfb6 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -42,4 +42,10 @@ <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, when the PIP menu is shown in center. --> <string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string> + + <!-- one handed background panel default color RGB --> + <item name="config_one_handed_background_rgb" format="float" type="dimen">0.5</item> + + <!-- one handed background panel default alpha --> + <item name="config_one_handed_background_alpha" format="float" type="dimen">0.5</item> </resources> 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 63d31182a748..0146b728bcad 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 androidx.annotation.NonNull; 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; @@ -55,19 +56,16 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d", taskInfo.taskId); mLeashByTaskId.put(taskInfo.taskId, leash); + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; final Point positionInParent = taskInfo.positionInParent; mSyncQueue.runInSync(t -> { // Reset several properties back to fullscreen (PiP, for example, leaves all these // properties in a bad state). t.setWindowCrop(leash, null); t.setPosition(leash, positionInParent.x, positionInParent.y); - // TODO(shell-transitions): Eventually set everything in transition so there's no - // SF Transaction here. - if (!Transitions.ENABLE_SHELL_TRANSITIONS) { - t.setAlpha(leash, 1f); - t.setMatrix(leash, 1, 0, 0, 1); - t.show(leash); - } + t.setAlpha(leash, 1f); + t.setMatrix(leash, 1, 0, 0, 1); + t.show(leash); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java index 2f2168f53553..aa82339a436a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java @@ -16,113 +16,32 @@ package com.android.wm.shell; +import android.util.Slog; + import com.android.wm.shell.apppairs.AppPairs; -import com.android.wm.shell.common.annotations.ExternalThread; +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; import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import java.io.PrintWriter; import java.util.Optional; +import java.util.concurrent.TimeUnit; /** * An entry point into the shell for dumping shell internal state and running adb commands. * * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}. */ -public final class ShellCommandHandler { - - private final Optional<LegacySplitScreen> mLegacySplitScreenOptional; - private final Optional<Pip> mPipOptional; - private final Optional<OneHanded> mOneHandedOptional; - private final Optional<HideDisplayCutout> mHideDisplayCutout; - private final ShellTaskOrganizer mShellTaskOrganizer; - private final Optional<AppPairs> mAppPairsOptional; - - public ShellCommandHandler( - ShellTaskOrganizer shellTaskOrganizer, - Optional<LegacySplitScreen> legacySplitScreenOptional, - Optional<Pip> pipOptional, - Optional<OneHanded> oneHandedOptional, - Optional<HideDisplayCutout> hideDisplayCutout, - Optional<AppPairs> appPairsOptional) { - mShellTaskOrganizer = shellTaskOrganizer; - mLegacySplitScreenOptional = legacySplitScreenOptional; - mPipOptional = pipOptional; - mOneHandedOptional = oneHandedOptional; - mHideDisplayCutout = hideDisplayCutout; - mAppPairsOptional = appPairsOptional; - } - - /** Dumps WM Shell internal state. */ - @ExternalThread - public void dump(PrintWriter pw) { - mShellTaskOrganizer.dump(pw, ""); - pw.println(); - pw.println(); - mPipOptional.ifPresent(pip -> pip.dump(pw)); - mLegacySplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw)); - mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw)); - mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw)); - pw.println(); - pw.println(); - mAppPairsOptional.ifPresent(appPairs -> appPairs.dump(pw, "")); - } - - - /** Returns {@code true} if command was found and executed. */ - @ExternalThread - public boolean handleCommand(String[] args, PrintWriter pw) { - if (args.length < 2) { - // Argument at position 0 is "WMShell". - return false; - } - switch (args[1]) { - case "pair": - return runPair(args, pw); - case "unpair": - return runUnpair(args, pw); - case "help": - return runHelp(pw); - default: - return false; - } - } - - - private boolean runPair(String[] args, PrintWriter pw) { - if (args.length < 4) { - // First two arguments are "WMShell" and command name. - pw.println("Error: two task ids should be provided as arguments"); - return false; - } - final int taskId1 = new Integer(args[2]); - final int taskId2 = new Integer(args[3]); - mAppPairsOptional.ifPresent(appPairs -> appPairs.pair(taskId1, taskId2)); - return true; - } - - private boolean runUnpair(String[] args, PrintWriter pw) { - if (args.length < 3) { - // First two arguments are "WMShell" and command name. - pw.println("Error: task id should be provided as an argument"); - return false; - } - final int taskId = new Integer(args[2]); - mAppPairsOptional.ifPresent(appPairs -> appPairs.unpair(taskId)); - return true; - } - - private boolean runHelp(PrintWriter pw) { - pw.println("Window Manager Shell commands:"); - pw.println(" help"); - pw.println(" Print this help text."); - pw.println(" <no arguments provided>"); - pw.println(" Dump Window Manager Shell internal state"); - pw.println(" pair <taskId1> <taskId2>"); - pw.println(" unpair <taskId>"); - pw.println(" Pairs/unpairs tasks with given ids."); - return true; - } +public interface ShellCommandHandler { + /** + * Dumps the shell state. + */ + void dump(PrintWriter pw); + + /** + * Handles a shell command. + */ + boolean handleCommand(final String[] args, PrintWriter pw); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java new file mode 100644 index 000000000000..f213af752d55 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell; + +import android.util.Slog; + +import com.android.wm.shell.apppairs.AppPairs; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; +import com.android.wm.shell.onehanded.OneHanded; +import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; + +import java.io.PrintWriter; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * An entry point into the shell for dumping shell internal state and running adb commands. + * + * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}. + */ +public final class ShellCommandHandlerImpl { + private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName(); + + private final Optional<LegacySplitScreen> mLegacySplitScreenOptional; + private final Optional<Pip> mPipOptional; + private final Optional<OneHanded> mOneHandedOptional; + private final Optional<HideDisplayCutout> mHideDisplayCutout; + private final ShellTaskOrganizer mShellTaskOrganizer; + private final Optional<AppPairs> mAppPairsOptional; + private final ShellExecutor mMainExecutor; + private final HandlerImpl mImpl = new HandlerImpl(); + + public static ShellCommandHandler create( + ShellTaskOrganizer shellTaskOrganizer, + Optional<LegacySplitScreen> legacySplitScreenOptional, + Optional<Pip> pipOptional, + Optional<OneHanded> oneHandedOptional, + Optional<HideDisplayCutout> hideDisplayCutout, + Optional<AppPairs> appPairsOptional, + ShellExecutor mainExecutor) { + return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional, + pipOptional, oneHandedOptional, hideDisplayCutout, appPairsOptional, + mainExecutor).mImpl; + } + + private ShellCommandHandlerImpl( + ShellTaskOrganizer shellTaskOrganizer, + Optional<LegacySplitScreen> legacySplitScreenOptional, + Optional<Pip> pipOptional, + Optional<OneHanded> oneHandedOptional, + Optional<HideDisplayCutout> hideDisplayCutout, + Optional<AppPairs> appPairsOptional, + ShellExecutor mainExecutor) { + mShellTaskOrganizer = shellTaskOrganizer; + mLegacySplitScreenOptional = legacySplitScreenOptional; + mPipOptional = pipOptional; + mOneHandedOptional = oneHandedOptional; + mHideDisplayCutout = hideDisplayCutout; + mAppPairsOptional = appPairsOptional; + mMainExecutor = mainExecutor; + } + + /** Dumps WM Shell internal state. */ + private void dump(PrintWriter pw) { + mShellTaskOrganizer.dump(pw, ""); + pw.println(); + pw.println(); + mPipOptional.ifPresent(pip -> pip.dump(pw)); + mLegacySplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw)); + mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw)); + mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw)); + pw.println(); + pw.println(); + mAppPairsOptional.ifPresent(appPairs -> appPairs.dump(pw, "")); + } + + + /** Returns {@code true} if command was found and executed. */ + private boolean handleCommand(final String[] args, PrintWriter pw) { + if (args.length < 2) { + // Argument at position 0 is "WMShell". + return false; + } + switch (args[1]) { + case "pair": + return runPair(args, pw); + case "unpair": + return runUnpair(args, pw); + case "help": + return runHelp(pw); + default: + return false; + } + } + + + private boolean runPair(String[] args, PrintWriter pw) { + if (args.length < 4) { + // First two arguments are "WMShell" and command name. + pw.println("Error: two task ids should be provided as arguments"); + return false; + } + final int taskId1 = new Integer(args[2]); + final int taskId2 = new Integer(args[3]); + mAppPairsOptional.ifPresent(appPairs -> appPairs.pair(taskId1, taskId2)); + return true; + } + + private boolean runUnpair(String[] args, PrintWriter pw) { + if (args.length < 3) { + // First two arguments are "WMShell" and command name. + pw.println("Error: task id should be provided as an argument"); + return false; + } + final int taskId = new Integer(args[2]); + mAppPairsOptional.ifPresent(appPairs -> appPairs.unpair(taskId)); + return true; + } + + private boolean runHelp(PrintWriter pw) { + pw.println("Window Manager Shell commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" <no arguments provided>"); + pw.println(" Dump Window Manager Shell internal state"); + pw.println(" pair <taskId1> <taskId2>"); + pw.println(" unpair <taskId>"); + pw.println(" Pairs/unpairs tasks with given ids."); + return true; + } + + private class HandlerImpl implements ShellCommandHandler { + @Override + public void dump(PrintWriter pw) { + try { + mMainExecutor.executeBlocking(() -> ShellCommandHandlerImpl.this.dump(pw)); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to dump the Shell in 2s", e); + } + } + + @Override + public boolean handleCommand(String[] args, PrintWriter pw) { + try { + boolean[] result = new boolean[1]; + mMainExecutor.executeBlocking(() -> { + result[0] = ShellCommandHandlerImpl.this.handleCommand(args, pw); + }); + return result[0]; + } catch (InterruptedException e) { + throw new RuntimeException("Failed to handle Shell command in 2s", e); + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java index f4c617e601fc..d7010b174744 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java @@ -16,61 +16,15 @@ package com.android.wm.shell; -import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; - -import com.android.wm.shell.apppairs.AppPairs; -import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.draganddrop.DragAndDropController; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; - -import java.util.Optional; /** * An entry point into the shell for initializing shell internal state. */ -public class ShellInit { - - private final DisplayImeController mDisplayImeController; - private final DragAndDropController mDragAndDropController; - private final ShellTaskOrganizer mShellTaskOrganizer; - private final Optional<LegacySplitScreen> mLegacySplitScreenOptional; - private final Optional<AppPairs> mAppPairsOptional; - private final FullscreenTaskListener mFullscreenTaskListener; - private final Transitions mTransitions; - - public ShellInit(DisplayImeController displayImeController, - DragAndDropController dragAndDropController, - ShellTaskOrganizer shellTaskOrganizer, - Optional<LegacySplitScreen> legacySplitScreenOptional, - Optional<AppPairs> appPairsOptional, - FullscreenTaskListener fullscreenTaskListener, - Transitions transitions) { - mDisplayImeController = displayImeController; - mDragAndDropController = dragAndDropController; - mShellTaskOrganizer = shellTaskOrganizer; - mLegacySplitScreenOptional = legacySplitScreenOptional; - mAppPairsOptional = appPairsOptional; - mFullscreenTaskListener = fullscreenTaskListener; - mTransitions = transitions; - } - - @ExternalThread - public void init() { - // Start listening for display changes - mDisplayImeController.startMonitorDisplays(); - - mShellTaskOrganizer.addListenerForType( - mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN); - // Register the shell organizer - mShellTaskOrganizer.registerOrganizer(); - - mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered); - // Bind the splitscreen impl to the drag drop controller - mDragAndDropController.setSplitScreenController(mLegacySplitScreenOptional); - - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - mTransitions.register(mShellTaskOrganizer); - } - } +@ExternalThread +public interface ShellInit { + /** + * Initializes the shell state. + */ + void init(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java new file mode 100644 index 000000000000..0cee0a21bde3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell; + +import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; + +import com.android.wm.shell.apppairs.AppPairs; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.ShellExecutor; +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; + +/** + * The entry point implementation into the shell for initializing shell internal state. + */ +public class ShellInitImpl { + private static final String TAG = ShellInitImpl.class.getSimpleName(); + + private final DisplayImeController mDisplayImeController; + private final DragAndDropController mDragAndDropController; + private final ShellTaskOrganizer mShellTaskOrganizer; + private final Optional<LegacySplitScreen> mLegacySplitScreenOptional; + private final Optional<AppPairs> mAppPairsOptional; + private final FullscreenTaskListener mFullscreenTaskListener; + private final ShellExecutor mMainExecutor; + private final Transitions mTransitions; + + private final InitImpl mImpl = new InitImpl(); + + public static ShellInit create(DisplayImeController displayImeController, + DragAndDropController dragAndDropController, + ShellTaskOrganizer shellTaskOrganizer, + Optional<LegacySplitScreen> legacySplitScreenOptional, + Optional<AppPairs> appPairsOptional, + FullscreenTaskListener fullscreenTaskListener, + Transitions transitions, + ShellExecutor mainExecutor) { + return new ShellInitImpl(displayImeController, + dragAndDropController, + shellTaskOrganizer, + legacySplitScreenOptional, + appPairsOptional, + fullscreenTaskListener, + transitions, + mainExecutor).mImpl; + } + + private ShellInitImpl(DisplayImeController displayImeController, + DragAndDropController dragAndDropController, + ShellTaskOrganizer shellTaskOrganizer, + Optional<LegacySplitScreen> legacySplitScreenOptional, + Optional<AppPairs> appPairsOptional, + FullscreenTaskListener fullscreenTaskListener, + Transitions transitions, + ShellExecutor mainExecutor) { + mDisplayImeController = displayImeController; + mDragAndDropController = dragAndDropController; + mShellTaskOrganizer = shellTaskOrganizer; + mLegacySplitScreenOptional = legacySplitScreenOptional; + mAppPairsOptional = appPairsOptional; + mFullscreenTaskListener = fullscreenTaskListener; + mTransitions = transitions; + mMainExecutor = mainExecutor; + } + + private void init() { + // Start listening for display changes + mDisplayImeController.startMonitorDisplays(); + + mShellTaskOrganizer.addListenerForType( + mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN); + // Register the shell organizer + mShellTaskOrganizer.registerOrganizer(); + + mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered); + + // Bind the splitscreen impl to the drag drop controller + mDragAndDropController.initialize(mLegacySplitScreenOptional); + + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mTransitions.register(mShellTaskOrganizer); + } + } + + @ExternalThread + private class InitImpl implements ShellInit { + @Override + public void init() { + try { + mMainExecutor.executeBlocking(() -> ShellInitImpl.this.init()); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to initialize the Shell in 2s", e); + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java index d588419bbe19..8d0e9655f28d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java @@ -29,11 +29,14 @@ import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.graphics.Rect; +import android.graphics.Region; import android.os.Binder; import android.util.CloseGuard; import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.SurfaceView; +import android.view.View; +import android.view.ViewTreeObserver; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -44,7 +47,7 @@ import java.util.concurrent.Executor; * View that can display a task. */ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, - ShellTaskOrganizer.TaskListener { + ShellTaskOrganizer.TaskListener, ViewTreeObserver.OnComputeInternalInsetsListener { /** Callback for listening task state. */ public interface Listener { @@ -70,6 +73,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, private final CloseGuard mGuard = new CloseGuard(); private final ShellTaskOrganizer mTaskOrganizer; + private final Executor mShellExecutor; private ActivityManager.RunningTaskInfo mTaskInfo; private WindowContainerToken mTaskToken; @@ -78,16 +82,17 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, private boolean mSurfaceCreated; private boolean mIsInitialized; private Listener mListener; - private Executor mExecutor; + private Executor mListenerExecutor; private final Rect mTmpRect = new Rect(); private final Rect mTmpRootRect = new Rect(); + private final int[] mTmpLocation = new int[2]; public TaskView(Context context, ShellTaskOrganizer organizer) { super(context, null, 0, 0, true /* disableBackgroundLayer */); mTaskOrganizer = organizer; - mExecutor = organizer.getExecutor(); + mShellExecutor = organizer.getExecutor(); setUseAlpha(); getHolder().addCallback(this); mGuard.open("release"); @@ -96,12 +101,13 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, /** * Only one listener may be set on the view, throws an exception otherwise. */ - public void setListener(Listener listener) { + public void setListener(@NonNull Executor executor, Listener listener) { if (mListener != null) { throw new IllegalStateException( "Trying to set a listener when one has already been set"); } mListener = listener; + mListenerExecutor = executor; } /** @@ -146,7 +152,9 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, private void prepareActivityOptions(ActivityOptions options) { final Binder launchCookie = new Binder(); - mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this); + mShellExecutor.execute(() -> { + mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this); + }); options.setLaunchCookie(launchCookie); options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); options.setTaskAlwaysOnTop(true); @@ -193,11 +201,15 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, private void performRelease() { getHolder().removeCallback(this); - mTaskOrganizer.removeListener(this); - resetTaskInfo(); + mShellExecutor.execute(() -> { + mTaskOrganizer.removeListener(this); + resetTaskInfo(); + }); mGuard.close(); if (mListener != null && mIsInitialized) { - mListener.onReleased(); + mListenerExecutor.execute(() -> { + mListener.onReleased(); + }); mIsInitialized = false; } } @@ -214,75 +226,71 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, mTaskOrganizer.applyTransaction(wct); // TODO(b/151449487): Only call callback once we enable synchronization if (mListener != null) { - mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated); + mListenerExecutor.execute(() -> { + mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated); + }); } } @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - if (mExecutor == null) return; - mExecutor.execute(() -> { - mTaskInfo = taskInfo; - mTaskToken = taskInfo.token; - mTaskLeash = leash; - - if (mSurfaceCreated) { - // Surface is ready, so just reparent the task to this surface control - mTransaction.reparent(mTaskLeash, getSurfaceControl()) - .show(mTaskLeash) - .apply(); - } else { - // The surface has already been destroyed before the task has appeared, - // so go ahead and hide the task entirely - updateTaskVisibility(); - } - mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true); - // TODO: Synchronize show with the resize - onLocationChanged(); - setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor()); + mTaskInfo = taskInfo; + mTaskToken = taskInfo.token; + mTaskLeash = leash; + + if (mSurfaceCreated) { + // Surface is ready, so just reparent the task to this surface control + mTransaction.reparent(mTaskLeash, getSurfaceControl()) + .show(mTaskLeash) + .apply(); + } else { + // The surface has already been destroyed before the task has appeared, + // so go ahead and hide the task entirely + updateTaskVisibility(); + } + mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true); + // TODO: Synchronize show with the resize + onLocationChanged(); + setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor()); - if (mListener != null) { + if (mListener != null) { + mListenerExecutor.execute(() -> { mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity); - } - }); + }); + } } @Override public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { - if (mExecutor == null) return; - mExecutor.execute(() -> { - if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return; + if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return; - if (mListener != null) { + if (mListener != null) { + mListenerExecutor.execute(() -> { mListener.onTaskRemovalStarted(taskInfo.taskId); - } - mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false); + }); + } + mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false); - // Unparent the task when this surface is destroyed - mTransaction.reparent(mTaskLeash, null).apply(); - resetTaskInfo(); - }); + // Unparent the task when this surface is destroyed + mTransaction.reparent(mTaskLeash, null).apply(); + resetTaskInfo(); } @Override public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { - if (mExecutor == null) return; - mExecutor.execute(() -> { - mTaskInfo.taskDescription = taskInfo.taskDescription; - setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor()); - }); + mTaskInfo.taskDescription = taskInfo.taskDescription; + setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor()); } @Override public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { - if (mExecutor == null) return; - mExecutor.execute(() -> { - if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return; - if (mListener != null) { + if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return; + if (mListener != null) { + mListenerExecutor.execute(() -> { mListener.onBackPressedOnTaskRoot(taskInfo.taskId); - } - }); + }); + } } @Override @@ -302,17 +310,21 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, mSurfaceCreated = true; if (mListener != null && !mIsInitialized) { mIsInitialized = true; - mListener.onInitialized(); - } - if (mTaskToken == null) { - // Nothing to update, task is not yet available - return; + mListenerExecutor.execute(() -> { + mListener.onInitialized(); + }); } - // Reparent the task when this surface is created - mTransaction.reparent(mTaskLeash, getSurfaceControl()) - .show(mTaskLeash) - .apply(); - updateTaskVisibility(); + mShellExecutor.execute(() -> { + if (mTaskToken == null) { + // Nothing to update, task is not yet available + return; + } + // Reparent the task when this surface is created + mTransaction.reparent(mTaskLeash, getSurfaceControl()) + .show(mTaskLeash) + .apply(); + updateTaskVisibility(); + }); } @Override @@ -326,13 +338,47 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, @Override public void surfaceDestroyed(SurfaceHolder holder) { mSurfaceCreated = false; - if (mTaskToken == null) { - // Nothing to update, task is not yet available - return; + mShellExecutor.execute(() -> { + if (mTaskToken == null) { + // Nothing to update, task is not yet available + return; + } + + // Unparent the task when this surface is destroyed + mTransaction.reparent(mTaskLeash, null).apply(); + updateTaskVisibility(); + }); + } + + @Override + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { + // TODO(b/176854108): Consider to move the logic into gatherTransparentRegions since this + // is dependent on the order of listener. + // If there are multiple TaskViews, we'll set the touchable area as the root-view, then + // subtract each TaskView from it. + if (inoutInfo.touchableRegion.isEmpty()) { + inoutInfo.setTouchableInsets( + ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + View root = getRootView(); + root.getLocationInWindow(mTmpLocation); + mTmpRootRect.set(mTmpLocation[0], mTmpLocation[1], root.getWidth(), root.getHeight()); + inoutInfo.touchableRegion.set(mTmpRootRect); } + getLocationInWindow(mTmpLocation); + mTmpRect.set(mTmpLocation[0], mTmpLocation[1], + mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight()); + inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE); + } - // Unparent the task when this surface is destroyed - mTransaction.reparent(mTaskLeash, null).apply(); - updateTaskVisibility(); + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + getViewTreeObserver().addOnComputeInternalInsetsListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getViewTreeObserver().removeOnComputeInternalInsetsListener(this); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactory.java new file mode 100644 index 000000000000..a29e7a085a21 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell; + +import android.annotation.UiContext; +import android.content.Context; + +import com.android.wm.shell.common.annotations.ExternalThread; + +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** Interface to create TaskView. */ +@ExternalThread +public interface TaskViewFactory { + /** Creates an {@link TaskView} */ + void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java new file mode 100644 index 000000000000..a5dd79b373bd --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.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.wm.shell; + +import android.annotation.UiContext; +import android.content.Context; + +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.common.annotations.ShellMainThread; + +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** Factory controller which can create {@link TaskView} */ +public class TaskViewFactoryController { + private final ShellTaskOrganizer mTaskOrganizer; + private final ShellExecutor mShellExecutor; + + public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer, + ShellExecutor shellExecutor) { + mTaskOrganizer = taskOrganizer; + mShellExecutor = shellExecutor; + } + + /** Creates an {@link TaskView} */ + @ShellMainThread + public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) { + TaskView taskView = new TaskView(context, mTaskOrganizer); + executor.execute(() -> { + onCreate.accept(taskView); + }); + } + + public TaskViewFactory getTaskViewFactory() { + return new TaskViewFactoryImpl(); + } + + private class TaskViewFactoryImpl implements TaskViewFactory { + @ExternalThread + public void create(@UiContext Context context, + Executor executor, Consumer<TaskView> onCreate) { + mShellExecutor.execute(() -> { + TaskViewFactoryController.this.create(context, executor, onCreate); + }); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java index 834de3f15b1d..abd92577c1d4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java @@ -37,8 +37,8 @@ public class WindowManagerShellWrapper { */ private final PinnedStackListenerForwarder mPinnedStackListenerForwarder; - public WindowManagerShellWrapper(ShellExecutor shellMainExecutor) { - mPinnedStackListenerForwarder = new PinnedStackListenerForwarder(shellMainExecutor); + public WindowManagerShellWrapper(ShellExecutor mainExecutor) { + mPinnedStackListenerForwarder = new PinnedStackListenerForwarder(mainExecutor); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 40b41e11c8aa..9419b9c6d344 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -45,7 +45,6 @@ import android.graphics.PixelFormat; import android.graphics.PointF; import android.os.Binder; import android.os.Bundle; -import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -68,6 +67,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import java.io.FileDescriptor; @@ -106,7 +106,6 @@ public class BubbleController implements Bubbles { private final FloatingContentCoordinator mFloatingContentCoordinator; private final BubbleDataRepository mDataRepository; private BubbleLogger mLogger; - private final Handler mMainHandler; private BubbleData mBubbleData; private View mBubbleScrim; @Nullable private BubbleStackView mStackView; @@ -138,7 +137,7 @@ public class BubbleController implements Bubbles { private WindowManager mWindowManager; // Used to post to main UI thread - private Handler mHandler = new Handler(); + private final ShellExecutor mMainExecutor; /** LayoutParams used to add the BubbleStackView to the window manager. */ private WindowManager.LayoutParams mWmLayoutParams; @@ -186,15 +185,15 @@ public class BubbleController implements Bubbles { WindowManagerShellWrapper windowManagerShellWrapper, LauncherApps launcherApps, UiEventLogger uiEventLogger, - Handler mainHandler, - ShellTaskOrganizer organizer) { + ShellTaskOrganizer organizer, + ShellExecutor mainExecutor) { BubbleLogger logger = new BubbleLogger(uiEventLogger); BubblePositioner positioner = new BubblePositioner(context, windowManager); BubbleData data = new BubbleData(context, logger, positioner); return new BubbleController(context, data, synchronizer, floatingContentCoordinator, new BubbleDataRepository(context, launcherApps), statusBarService, windowManager, windowManagerShellWrapper, launcherApps, - logger, mainHandler, organizer, positioner); + logger, organizer, positioner, mainExecutor); } /** @@ -211,14 +210,14 @@ public class BubbleController implements Bubbles { WindowManagerShellWrapper windowManagerShellWrapper, LauncherApps launcherApps, BubbleLogger bubbleLogger, - Handler mainHandler, ShellTaskOrganizer organizer, - BubblePositioner positioner) { + BubblePositioner positioner, + ShellExecutor mainExecutor) { mContext = context; mFloatingContentCoordinator = floatingContentCoordinator; mDataRepository = dataRepository; mLogger = bubbleLogger; - mMainHandler = mainHandler; + mMainExecutor = mainExecutor; mBubblePositioner = positioner; mBubbleData = data; @@ -241,7 +240,7 @@ public class BubbleController implements Bubbles { bubble.setPendingIntentCanceled(); return; } - mHandler.post(() -> removeBubble(bubble.getKey(), DISMISS_INVALID_INTENT)); + mMainExecutor.execute(() -> removeBubble(bubble.getKey(), DISMISS_INVALID_INTENT)); }); try { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 7f3389599a51..cb6b54321b5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -314,7 +314,7 @@ public class BubbleExpandedView extends LinearLayout { mTaskView = new TaskView(mContext, mController.getTaskOrganizer()); mExpandedViewContainer.addView(mTaskView); bringChildToFront(mTaskView); - mTaskView.setListener(mTaskViewListener); + mTaskView.setListener(mContext.getMainExecutor(), mTaskViewListener); mPositioner = mController.getPositioner(); } @@ -471,13 +471,12 @@ public class BubbleExpandedView extends LinearLayout { mIsOverflow = overflow; Intent target = new Intent(mContext, BubbleOverflowActivity.class); + target.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK); Bundle extras = new Bundle(); extras.putBinder(EXTRA_BUBBLE_CONTROLLER, ObjectWrapper.wrap(mController)); target.putExtras(extras); - // TODO(b/175352055) Please replace FLAG_MUTABLE_UNAUDITED below - // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE. mPendingIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */, - target, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); + target, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); mSettingsIcon.setVisibility(GONE); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 1e5e43a99b09..0f81d7ec6ae6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1201,15 +1201,7 @@ public class BubbleStackView extends FrameLayout mTempRect.setEmpty(); getTouchableRegion(mTempRect); - if (mIsExpanded && mExpandedBubble != null - && mExpandedBubble.getExpandedView() != null - && mExpandedBubble.getExpandedView().getTaskView() != null) { - inoutInfo.touchableRegion.set(mTempRect); - mExpandedBubble.getExpandedView().getTaskView().getBoundsOnScreen(mTempRect); - inoutInfo.touchableRegion.op(mTempRect, Region.Op.DIFFERENCE); - } else { - inoutInfo.touchableRegion.set(mTempRect); - } + inoutInfo.touchableRegion.set(mTempRect); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java index cb4584c41184..3a7b534f3c17 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java @@ -16,7 +16,6 @@ package com.android.wm.shell.common; -import android.os.Handler; import android.os.RemoteException; import android.view.IDisplayWindowRotationCallback; import android.view.IDisplayWindowRotationController; @@ -37,7 +36,7 @@ import java.util.ArrayList; */ public class DisplayChangeController { - private final Handler mHandler; + private final ShellExecutor mMainExecutor; private final IWindowManager mWmService; private final IDisplayWindowRotationController mControllerImpl; @@ -45,8 +44,8 @@ public class DisplayChangeController { new ArrayList<>(); private final ArrayList<OnDisplayChangingListener> mTmpListeners = new ArrayList<>(); - public DisplayChangeController(Handler mainHandler, IWindowManager wmService) { - mHandler = mainHandler; + public DisplayChangeController(IWindowManager wmService, ShellExecutor mainExecutor) { + mMainExecutor = mainExecutor; mWmService = wmService; mControllerImpl = new DisplayWindowRotationControllerImpl(); try { @@ -97,7 +96,7 @@ public class DisplayChangeController { @Override public void onRotateDisplay(int displayId, final int fromRotation, final int toRotation, IDisplayWindowRotationCallback callback) { - mHandler.post(() -> { + mMainExecutor.execute(() -> { DisplayChangeController.this.onRotateDisplay(displayId, fromRotation, toRotation, callback); }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index a413c052cb6c..ba9ba5e5883a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -20,7 +20,6 @@ import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.hardware.display.DisplayManager; -import android.os.Handler; import android.os.RemoteException; import android.util.Slog; import android.util.SparseArray; @@ -44,7 +43,7 @@ import java.util.ArrayList; public class DisplayController { private static final String TAG = "DisplayController"; - private final Handler mHandler; + private final ShellExecutor mMainExecutor; private final Context mContext; private final IWindowManager mWmService; private final DisplayChangeController mChangeController; @@ -61,12 +60,12 @@ public class DisplayController { return displayManager.getDisplay(displayId); } - public DisplayController(Context context, Handler handler, - IWindowManager wmService) { - mHandler = handler; + public DisplayController(Context context, IWindowManager wmService, + ShellExecutor mainExecutor) { + mMainExecutor = mainExecutor; mContext = context; mWmService = wmService; - mChangeController = new DisplayChangeController(mHandler, mWmService); + mChangeController = new DisplayChangeController(mWmService, mainExecutor); mDisplayContainerListener = new DisplayWindowListenerImpl(); try { mWmService.registerDisplayWindowListener(mDisplayContainerListener); @@ -229,35 +228,35 @@ public class DisplayController { private class DisplayWindowListenerImpl extends IDisplayWindowListener.Stub { @Override public void onDisplayAdded(int displayId) { - mHandler.post(() -> { + mMainExecutor.execute(() -> { DisplayController.this.onDisplayAdded(displayId); }); } @Override public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { - mHandler.post(() -> { + mMainExecutor.execute(() -> { DisplayController.this.onDisplayConfigurationChanged(displayId, newConfig); }); } @Override public void onDisplayRemoved(int displayId) { - mHandler.post(() -> { + mMainExecutor.execute(() -> { DisplayController.this.onDisplayRemoved(displayId); }); } @Override public void onFixedRotationStarted(int displayId, int newRotation) { - mHandler.post(() -> { + mMainExecutor.execute(() -> { DisplayController.this.onFixedRotationStarted(displayId, newRotation); }); } @Override public void onFixedRotationFinished(int displayId) { - mHandler.post(() -> { + mMainExecutor.execute(() -> { DisplayController.this.onFixedRotationFinished(displayId); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index 3fbd7ed0ec5d..39441281f670 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -40,10 +40,12 @@ import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import androidx.annotation.BinderThread; +import androidx.annotation.VisibleForTesting; import com.android.internal.view.IInputMethodManager; import java.util.ArrayList; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -64,7 +66,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged private static final int FLOATING_IME_BOTTOM_INSET = -80; protected final IWindowManager mWmService; - protected final Executor mExecutor; + protected final Executor mMainExecutor; private final TransactionPool mTransactionPool; private final DisplayController mDisplayController; private final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>(); @@ -73,10 +75,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged public DisplayImeController(IWindowManager wmService, DisplayController displayController, Executor mainExecutor, TransactionPool transactionPool) { - mExecutor = mainExecutor; mWmService = wmService; - mTransactionPool = transactionPool; mDisplayController = displayController; + mMainExecutor = mainExecutor; + mTransactionPool = transactionPool; } /** Starts monitor displays changes and set insets controller for each displays. */ @@ -90,11 +92,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged // WM will defer IME inset handling to it in multi-window scenarious. PerDisplay pd = new PerDisplay(displayId, mDisplayController.getDisplayLayout(displayId).rotation()); - try { - mWmService.setDisplayWindowInsetsController(displayId, pd); - } catch (RemoteException e) { - Slog.w(TAG, "Unable to set insets controller on display " + displayId); - } + pd.register(); mImePerDisplay.put(displayId, pd); } @@ -182,9 +180,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } /** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */ - public class PerDisplay extends IDisplayWindowInsetsController.Stub { + public class PerDisplay { final int mDisplayId; final InsetsState mInsetsState = new InsetsState(); + protected final DisplayWindowInsetsControllerImpl mInsetsControllerImpl = + new DisplayWindowInsetsControllerImpl(); InsetsSourceControl mImeSourceControl = null; int mAnimationDirection = DIRECTION_NONE; ValueAnimator mAnimation = null; @@ -198,10 +198,16 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged mRotation = initialRotation; } - @BinderThread - @Override - public void insetsChanged(InsetsState insetsState) { - mExecutor.execute(() -> { + public void register() { + try { + mWmService.setDisplayWindowInsetsController(mDisplayId, mInsetsControllerImpl); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to set insets controller on display " + mDisplayId); + } + } + + protected void insetsChanged(InsetsState insetsState) { + mMainExecutor.execute(() -> { if (mInsetsState.equals(insetsState)) { return; } @@ -220,9 +226,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged }); } - @BinderThread - @Override - public void insetsControlChanged(InsetsState insetsState, + @VisibleForTesting + protected void insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls) { insetsChanged(insetsState); if (activeControls != null) { @@ -231,7 +236,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged continue; } if (activeControl.getType() == InsetsState.ITYPE_IME) { - mExecutor.execute(() -> { + mMainExecutor.execute(() -> { final Point lastSurfacePosition = mImeSourceControl != null ? mImeSourceControl.getSurfacePosition() : null; final boolean positionChanged = @@ -271,30 +276,25 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } } - @BinderThread - @Override - public void showInsets(int types, boolean fromIme) { + protected void showInsets(int types, boolean fromIme) { if ((types & WindowInsets.Type.ime()) == 0) { return; } if (DEBUG) Slog.d(TAG, "Got showInsets for ime"); - mExecutor.execute(() -> startAnimation(true /* show */, false /* forceRestart */)); + mMainExecutor.execute(() -> startAnimation(true /* show */, false /* forceRestart */)); } - @BinderThread - @Override - public void hideInsets(int types, boolean fromIme) { + + protected void hideInsets(int types, boolean fromIme) { if ((types & WindowInsets.Type.ime()) == 0) { return; } if (DEBUG) Slog.d(TAG, "Got hideInsets for ime"); - mExecutor.execute(() -> startAnimation(false /* show */, false /* forceRestart */)); + mMainExecutor.execute(() -> startAnimation(false /* show */, false /* forceRestart */)); } - @BinderThread - @Override public void topFocusedWindowChanged(String packageName) { - // no-op + // Do nothing } /** @@ -457,6 +457,47 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged setVisibleDirectly(true /* visible */); } } + + @VisibleForTesting + @BinderThread + public class DisplayWindowInsetsControllerImpl + extends IDisplayWindowInsetsController.Stub { + @Override + public void topFocusedWindowChanged(String packageName) throws RemoteException { + mMainExecutor.execute(() -> { + PerDisplay.this.topFocusedWindowChanged(packageName); + }); + } + + @Override + public void insetsChanged(InsetsState insetsState) throws RemoteException { + mMainExecutor.execute(() -> { + PerDisplay.this.insetsChanged(insetsState); + }); + } + + @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) throws RemoteException { + mMainExecutor.execute(() -> { + PerDisplay.this.insetsControlChanged(insetsState, activeControls); + }); + } + + @Override + public void showInsets(int types, boolean fromIme) throws RemoteException { + mMainExecutor.execute(() -> { + PerDisplay.this.showInsets(types, fromIme); + }); + } + + @Override + public void hideInsets(int types, boolean fromIme) throws RemoteException { + mMainExecutor.execute(() -> { + PerDisplay.this.hideInsets(types, fromIme); + }); + } + } } void removeImeSurface() { 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 22b831b7565e..f2c57f71f5b8 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 @@ -51,6 +51,16 @@ public interface ShellExecutor extends Executor { } /** + * Convenience method to execute the blocking call with a default timeout. + * + * @throws InterruptedException if runnable does not return in the time specified by + * {@param waitTimeout} + */ + default void executeBlocking(Runnable runnable) throws InterruptedException { + executeBlocking(runnable, 2, TimeUnit.SECONDS); + } + + /** * See {@link android.os.Handler#postDelayed(Runnable, long)}. */ void executeDelayed(Runnable r, long delayMillis); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java index 7321dc88770d..33beab5ee3f1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java @@ -16,16 +16,14 @@ package com.android.wm.shell.common; +import android.annotation.BinderThread; import android.annotation.NonNull; -import android.os.Handler; import android.util.Slog; import android.view.SurfaceControl; import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; import android.window.WindowOrganizer; -import androidx.annotation.BinderThread; - import com.android.wm.shell.common.annotations.ShellMainThread; import java.util.ArrayList; @@ -41,7 +39,7 @@ public final class SyncTransactionQueue { private static final int REPLY_TIMEOUT = 5300; private final TransactionPool mTransactionPool; - private final Handler mHandler; + private final ShellExecutor mMainExecutor; // Sync Transactions currently don't support nesting or interleaving properly, so // queue up transactions to run them serially. @@ -59,9 +57,9 @@ public final class SyncTransactionQueue { } }; - public SyncTransactionQueue(TransactionPool pool, Handler handler) { + public SyncTransactionQueue(TransactionPool pool, ShellExecutor mainExecutor) { mTransactionPool = pool; - mHandler = handler; + mMainExecutor = mainExecutor; } /** @@ -152,14 +150,14 @@ public final class SyncTransactionQueue { if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT); mId = new WindowOrganizer().applySyncTransaction(mWCT, this); if (DEBUG) Slog.d(TAG, " Sent sync transaction. Got id=" + mId); - mHandler.postDelayed(mOnReplyTimeout, REPLY_TIMEOUT); + mMainExecutor.executeDelayed(mOnReplyTimeout, REPLY_TIMEOUT); } @BinderThread @Override public void onTransactionReady(int id, @NonNull SurfaceControl.Transaction t) { - mHandler.post(() -> { + mMainExecutor.execute(() -> { synchronized (mQueue) { if (mId != id) { Slog.e(TAG, "Got an unexpected onTransactionReady. Expected " @@ -167,7 +165,7 @@ public final class SyncTransactionQueue { return; } mInFlight = null; - mHandler.removeCallbacks(mOnReplyTimeout); + mMainExecutor.removeCallbacks(mOnReplyTimeout); if (DEBUG) Slog.d(TAG, "onTransactionReady id=" + mId); mQueue.remove(this); onTransactionReceived(t); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java index 718f7a04508e..db34248d491b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java @@ -71,11 +71,11 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler. private final IActivityTaskManager mActivityTaskManager; // NOTE: In this case we do want to use a handler since we rely on the message system to // efficiently dedupe sequential calls - private Handler mHandler; + private Handler mMainHandler; - public TaskStackListenerImpl(Handler handler) { + public TaskStackListenerImpl(Handler mainHandler) { mActivityTaskManager = ActivityTaskManager.getService(); - mHandler = new Handler(handler.getLooper(), this); + mMainHandler = new Handler(mainHandler.getLooper(), this); } @VisibleForTesting @@ -84,8 +84,8 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler. } @VisibleForTesting - void setHandler(Handler handler) { - mHandler = handler; + void setHandler(Handler mainHandler) { + mMainHandler = mainHandler; } public void addListener(TaskStackListenerCallback listener) { @@ -124,13 +124,13 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler. @Override public void onRecentTaskListUpdated() { - mHandler.obtainMessage(ON_TASK_LIST_UPDATED).sendToTarget(); + mMainHandler.obtainMessage(ON_TASK_LIST_UPDATED).sendToTarget(); } @Override public void onRecentTaskListFrozenChanged(boolean frozen) { - mHandler.obtainMessage(ON_TASK_LIST_FROZEN_UNFROZEN, frozen ? 1 : 0, 0 /* unused */) - .sendToTarget(); + mMainHandler.obtainMessage(ON_TASK_LIST_FROZEN_UNFROZEN, frozen ? 1 : 0, + 0 /* unused */).sendToTarget(); } @Override @@ -147,48 +147,50 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler. } mTmpListeners.clear(); - mHandler.removeMessages(ON_TASK_STACK_CHANGED); - mHandler.sendEmptyMessage(ON_TASK_STACK_CHANGED); + mMainHandler.removeMessages(ON_TASK_STACK_CHANGED); + mMainHandler.sendEmptyMessage(ON_TASK_STACK_CHANGED); } @Override public void onTaskProfileLocked(int taskId, int userId) { - mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget(); + mMainHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget(); } @Override public void onTaskDisplayChanged(int taskId, int newDisplayId) { - mHandler.obtainMessage(ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget(); + mMainHandler.obtainMessage(ON_TASK_DISPLAY_CHANGED, taskId, + newDisplayId).sendToTarget(); } @Override public void onTaskCreated(int taskId, ComponentName componentName) { - mHandler.obtainMessage(ON_TASK_CREATED, taskId, 0, componentName).sendToTarget(); + mMainHandler.obtainMessage(ON_TASK_CREATED, taskId, 0, componentName).sendToTarget(); } @Override public void onTaskRemoved(int taskId) { - mHandler.obtainMessage(ON_TASK_REMOVED, taskId, 0).sendToTarget(); + mMainHandler.obtainMessage(ON_TASK_REMOVED, taskId, 0).sendToTarget(); } @Override public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { - mHandler.obtainMessage(ON_TASK_MOVED_TO_FRONT, taskInfo).sendToTarget(); + mMainHandler.obtainMessage(ON_TASK_MOVED_TO_FRONT, taskInfo).sendToTarget(); } @Override public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo) { - mHandler.obtainMessage(ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget(); + mMainHandler.obtainMessage(ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget(); } @Override public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { - mHandler.obtainMessage(ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget(); + mMainHandler.obtainMessage(ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot) + .sendToTarget(); } @Override public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { - mHandler.obtainMessage(ON_BACK_PRESSED_ON_TASK_ROOT, taskInfo).sendToTarget(); + mMainHandler.obtainMessage(ON_BACK_PRESSED_ON_TASK_ROOT, taskInfo).sendToTarget(); } @Override @@ -198,44 +200,44 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler. args.argi1 = userId; args.argi2 = taskId; args.argi3 = stackId; - mHandler.removeMessages(ON_ACTIVITY_PINNED); - mHandler.obtainMessage(ON_ACTIVITY_PINNED, args).sendToTarget(); + mMainHandler.removeMessages(ON_ACTIVITY_PINNED); + mMainHandler.obtainMessage(ON_ACTIVITY_PINNED, args).sendToTarget(); } @Override public void onActivityUnpinned() { - mHandler.removeMessages(ON_ACTIVITY_UNPINNED); - mHandler.sendEmptyMessage(ON_ACTIVITY_UNPINNED); + mMainHandler.removeMessages(ON_ACTIVITY_UNPINNED); + mMainHandler.sendEmptyMessage(ON_ACTIVITY_UNPINNED); } @Override - public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, - boolean clearedTask, boolean wasVisible) { + public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, + boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { final SomeArgs args = SomeArgs.obtain(); args.arg1 = task; args.argi1 = homeTaskVisible ? 1 : 0; args.argi2 = clearedTask ? 1 : 0; args.argi3 = wasVisible ? 1 : 0; - mHandler.removeMessages(ON_ACTIVITY_RESTART_ATTEMPT); - mHandler.obtainMessage(ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget(); + mMainHandler.removeMessages(ON_ACTIVITY_RESTART_ATTEMPT); + mMainHandler.obtainMessage(ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget(); } @Override public void onActivityForcedResizable(String packageName, int taskId, int reason) { - mHandler.obtainMessage(ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName) + mMainHandler.obtainMessage(ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName) .sendToTarget(); } @Override public void onActivityDismissingDockedStack() { - mHandler.sendEmptyMessage(ON_ACTIVITY_DISMISSING_DOCKED_STACK); + mMainHandler.sendEmptyMessage(ON_ACTIVITY_DISMISSING_DOCKED_STACK); } @Override public void onActivityLaunchOnSecondaryDisplayFailed( ActivityManager.RunningTaskInfo taskInfo, int requestedDisplayId) { - mHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED, + mMainHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED, requestedDisplayId, 0 /* unused */, taskInfo).sendToTarget(); @@ -245,25 +247,25 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler. public void onActivityLaunchOnSecondaryDisplayRerouted( ActivityManager.RunningTaskInfo taskInfo, int requestedDisplayId) { - mHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED, + mMainHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED, requestedDisplayId, 0 /* unused */, taskInfo).sendToTarget(); } @Override public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { - mHandler.obtainMessage(ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId, + mMainHandler.obtainMessage(ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId, requestedOrientation).sendToTarget(); } @Override public void onActivityRotation(int displayId) { - mHandler.obtainMessage(ON_ACTIVITY_ROTATION, displayId, 0 /* unused */) + mMainHandler.obtainMessage(ON_ACTIVITY_ROTATION, displayId, 0 /* unused */) .sendToTarget(); } @Override public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) { - mHandler.obtainMessage(ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId, + mMainHandler.obtainMessage(ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId, 0 /* unused */, activityToken).sendToTarget(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index 8ae66790eb3b..c8b4e10d4bb6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -74,11 +74,11 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange public DragAndDropController(Context context, DisplayController displayController) { mContext = context; mDisplayController = displayController; - mDisplayController.addDisplayWindowListener(this); } - public void setSplitScreenController(Optional<LegacySplitScreen> splitscreen) { + public void initialize(Optional<LegacySplitScreen> splitscreen) { mLegacySplitScreen = splitscreen.orElse(null); + mDisplayController.addDisplayWindowListener(this); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java index b92846fcaf94..a89100526b8b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java @@ -25,6 +25,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; import java.io.PrintWriter; import java.util.concurrent.Executor; @@ -37,12 +38,15 @@ public class HideDisplayCutoutController implements HideDisplayCutout { private final Context mContext; private final HideDisplayCutoutOrganizer mOrganizer; + private final ShellExecutor mMainExecutor; @VisibleForTesting boolean mEnabled; - HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer) { + HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer, + ShellExecutor mainExecutor) { mContext = context; mOrganizer = organizer; + mMainExecutor = mainExecutor; updateStatus(); } @@ -52,7 +56,7 @@ public class HideDisplayCutoutController implements HideDisplayCutout { */ @Nullable public static HideDisplayCutoutController create( - Context context, DisplayController displayController, Executor executor) { + Context context, DisplayController displayController, ShellExecutor mainExecutor) { // The SystemProperty is set for devices that support this feature and is used to control // whether to create the HideDisplayCutout instance. // It's defined in the device.mk (e.g. device/google/crosshatch/device.mk). @@ -61,8 +65,8 @@ public class HideDisplayCutoutController implements HideDisplayCutout { } HideDisplayCutoutOrganizer organizer = - new HideDisplayCutoutOrganizer(context, displayController, executor); - return new HideDisplayCutoutController(context, organizer); + new HideDisplayCutoutOrganizer(context, displayController, mainExecutor); + return new HideDisplayCutoutController(context, organizer, mainExecutor); } @VisibleForTesting diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java index 51a35d8514de..53dd391a01af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java @@ -42,6 +42,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.R; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; import java.io.PrintWriter; import java.util.List; @@ -90,8 +91,8 @@ class HideDisplayCutoutOrganizer extends DisplayAreaOrganizer { }; HideDisplayCutoutOrganizer(Context context, DisplayController displayController, - Executor executor) { - super(executor); + ShellExecutor mainExecutor) { + super(mainExecutor); mContext = context; mDisplayController = displayController; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java index 2c0cf5938474..7ce9014fc9ba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java @@ -24,7 +24,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.graphics.Rect; -import android.os.Handler; import android.util.Slog; import android.view.Choreographer; import android.view.SurfaceControl; @@ -33,6 +32,7 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; class DividerImeController implements DisplayImeController.ImePositionProcessor { @@ -43,7 +43,7 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor private final LegacySplitScreenTaskListener mSplits; private final TransactionPool mTransactionPool; - private final Handler mHandler; + private final ShellExecutor mMainExecutor; private final TaskOrganizer mTaskOrganizer; /** @@ -94,10 +94,10 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor private boolean mAdjustedWhileHidden = false; DividerImeController(LegacySplitScreenTaskListener splits, TransactionPool pool, - Handler handler, TaskOrganizer taskOrganizer) { + ShellExecutor mainExecutor, TaskOrganizer taskOrganizer) { mSplits = splits; mTransactionPool = pool; - mHandler = handler; + mMainExecutor = mainExecutor; mTaskOrganizer = taskOrganizer; } @@ -377,7 +377,7 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor /** Completely aborts/resets adjustment state */ public void pause(int displayId) { if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState()); - mHandler.post(() -> { + mMainExecutor.execute(() -> { if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState()); if (mPaused) { return; @@ -396,7 +396,7 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor public void resume(int displayId) { if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState()); - mHandler.post(() -> { + mMainExecutor.execute(() -> { if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState()); if (!mPaused) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java index ee5c9bce1289..139544f951ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java @@ -22,12 +22,12 @@ import static com.android.wm.shell.legacysplitscreen.ForcedResizableInfoActivity import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; -import android.os.Handler; import android.os.UserHandle; import android.util.ArraySet; import android.widget.Toast; import com.android.wm.shell.R; +import com.android.wm.shell.common.ShellExecutor; import java.util.function.Consumer; @@ -40,7 +40,7 @@ final class ForcedResizableInfoActivityController implements DividerView.Divider private static final int TIMEOUT = 1000; private final Context mContext; - private final Handler mHandler = new Handler(); + private final ShellExecutor mMainExecutor; private final ArraySet<PendingTaskRecord> mPendingTasks = new ArraySet<>(); private final ArraySet<String> mPackagesShownInSession = new ArraySet<>(); private boolean mDividerDragging; @@ -69,15 +69,17 @@ final class ForcedResizableInfoActivityController implements DividerView.Divider } ForcedResizableInfoActivityController(Context context, - LegacySplitScreenController splitScreenController) { + LegacySplitScreenController splitScreenController, + ShellExecutor mainExecutor) { mContext = context; + mMainExecutor = mainExecutor; splitScreenController.registerInSplitScreenListener(mDockedStackExistsListener); } @Override public void onDraggingStart() { mDividerDragging = true; - mHandler.removeCallbacks(mTimeoutRunnable); + mMainExecutor.removeCallbacks(mTimeoutRunnable); } @Override @@ -111,7 +113,7 @@ final class ForcedResizableInfoActivityController implements DividerView.Divider } private void showPending() { - mHandler.removeCallbacks(mTimeoutRunnable); + mMainExecutor.removeCallbacks(mTimeoutRunnable); for (int i = mPendingTasks.size() - 1; i >= 0; i--) { PendingTaskRecord pendingRecord = mPendingTasks.valueAt(i); Intent intent = new Intent(mContext, ForcedResizableInfoActivity.class); @@ -127,8 +129,8 @@ final class ForcedResizableInfoActivityController implements DividerView.Divider } private void postTimeout() { - mHandler.removeCallbacks(mTimeoutRunnable); - mHandler.postDelayed(mTimeoutRunnable, TIMEOUT); + mMainExecutor.removeCallbacks(mTimeoutRunnable); + mMainExecutor.executeDelayed(mTimeoutRunnable, TIMEOUT); } private boolean debounce(String packageName) { 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 6b79a3661bc0..a785cffb3df0 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 @@ -30,7 +30,6 @@ import android.app.ActivityTaskManager; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; -import android.os.Handler; import android.os.RemoteException; import android.provider.Settings; import android.util.Slog; @@ -48,11 +47,13 @@ import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; 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; @@ -79,7 +80,7 @@ public class LegacySplitScreenController implements LegacySplitScreen, private final DividerImeController mImePositionProcessor; private final DividerState mDividerState = new DividerState(); private final ForcedResizableInfoActivityController mForcedResizableController; - private final Handler mHandler; + private final ShellExecutor mMainExecutor; private final LegacySplitScreenTaskListener mSplits; private final SystemWindows mSystemWindows; final TransactionPool mTransactionPool; @@ -111,20 +112,23 @@ public class LegacySplitScreenController implements LegacySplitScreen, public LegacySplitScreenController(Context context, DisplayController displayController, SystemWindows systemWindows, - DisplayImeController imeController, Handler handler, TransactionPool transactionPool, + DisplayImeController imeController, TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, - TaskStackListenerImpl taskStackListener) { + TaskStackListenerImpl taskStackListener, Transitions transitions, + ShellExecutor mainExecutor) { mContext = context; mDisplayController = displayController; mSystemWindows = systemWindows; mImeController = imeController; - mHandler = handler; - mForcedResizableController = new ForcedResizableInfoActivityController(context, this); + mMainExecutor = mainExecutor; + mForcedResizableController = new ForcedResizableInfoActivityController(context, this, + mainExecutor); mTransactionPool = transactionPool; mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer); mTaskOrganizer = shellTaskOrganizer; - mSplits = new LegacySplitScreenTaskListener(this, shellTaskOrganizer, syncQueue); - mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler, + mSplits = new LegacySplitScreenTaskListener(this, shellTaskOrganizer, transitions, + syncQueue); + mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mMainExecutor, shellTaskOrganizer); mRotationController = (display, fromRotation, toRotation, wct) -> { @@ -269,11 +273,6 @@ public class LegacySplitScreenController implements LegacySplitScreen, } } - /** Posts task to handler dealing with divider. */ - void post(Runnable task) { - mHandler.post(task); - } - @Override public DividerView getDividerView() { return mView; @@ -343,7 +342,7 @@ public class LegacySplitScreenController implements LegacySplitScreen, } void onTaskVanished() { - mHandler.post(this::removeDivider); + removeDivider(); } private void updateVisibility(final boolean visible) { @@ -377,7 +376,7 @@ public class LegacySplitScreenController implements LegacySplitScreen, @Override public void setMinimized(final boolean minimized) { if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible); - mHandler.post(() -> { + mMainExecutor.execute(() -> { if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible); if (!mVisible) { return; @@ -553,8 +552,36 @@ public class LegacySplitScreenController implements LegacySplitScreen, mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout); } + void prepareEnterSplitTransition(WindowContainerTransaction outWct) { + // Set resizable directly here because buildEnterSplit already resizes home stack. + mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, mSplitLayout); + } + + void finishEnterSplitTransition(boolean minimized) { + update(mDisplayController.getDisplayContext( + mContext.getDisplayId()).getResources().getConfiguration()); + if (minimized) { + ensureMinimizedSplit(); + } else { + ensureNormalSplit(); + } + } + void startDismissSplit(boolean toPrimaryTask) { + startDismissSplit(toPrimaryTask, false /* snapped */); + } + + void startDismissSplit(boolean toPrimaryTask, boolean snapped) { + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mSplits.getSplitTransitions().dismissSplit( + mSplits, mSplitLayout, !toPrimaryTask, snapped); + } else { mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask); + onDismissSplit(); + } + } + + void onDismissSplit() { updateVisibility(false /* visible */); mMinimized = false; // Resets divider bar position to undefined, so new divider bar will apply default position 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 02c82dee8ca4..5a493c234ce3 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 @@ -32,13 +32,14 @@ import android.util.Log; import android.util.SparseArray; import android.view.SurfaceControl; import android.view.SurfaceSession; +import android.window.TaskOrganizer; import androidx.annotation.NonNull; 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; @@ -63,11 +64,17 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { final SurfaceSession mSurfaceSession = new SurfaceSession(); + private final SplitScreenTransitions mSplitTransitions; + LegacySplitScreenTaskListener(LegacySplitScreenController splitScreenController, ShellTaskOrganizer shellTaskOrganizer, + Transitions transitions, SyncTransactionQueue syncQueue) { mSplitScreenController = splitScreenController; mTaskOrganizer = shellTaskOrganizer; + mSplitTransitions = new SplitScreenTransitions(splitScreenController.mTransactionPool, + transitions, mSplitScreenController, this); + transitions.addHandler(mSplitTransitions); mSyncQueue = syncQueue; } @@ -98,6 +105,14 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { mSplitScreenController.mTransactionPool.release(t); } + TaskOrganizer getTaskOrganizer() { + return mTaskOrganizer; + } + + SplitScreenTransitions getSplitTransitions() { + return mSplitTransitions; + } + @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (this) { @@ -189,16 +204,18 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { return; } - mSplitScreenController.post(() -> handleTaskInfoChanged(taskInfo)); + handleTaskInfoChanged(taskInfo); } } private void handleChildTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { mLeashByTaskId.put(taskInfo.taskId, leash); + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); } private void handleChildTaskChanged(RunningTaskInfo taskInfo) { + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId); updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */); } @@ -241,14 +258,15 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { } else if (info.token.asBinder() == mSecondary.token.asBinder()) { mSecondary = info; } + if (DEBUG) { + Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary); + } + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED; final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED; final boolean secondaryImpliesMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS && mSplitScreenController.isHomeStackResizable()); - if (DEBUG) { - Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary); - } if (primaryIsEmpty == primaryWasEmpty && secondaryWasEmpty == secondaryIsEmpty && secondaryImpliedMinimize == secondaryImpliesMinimize) { // No relevant changes 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 new file mode 100644 index 000000000000..94b2cc0455bd --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java @@ -0,0 +1,342 @@ +/* + * 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.legacysplitscreen; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.WindowConfiguration; +import android.graphics.Rect; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; +import android.window.WindowContainerTransaction; + +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; + +/** Plays transition animations for split-screen */ +public class SplitScreenTransitions implements Transitions.TransitionHandler { + private static final String TAG = "SplitScreenTransitions"; + + public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 10; + + private final TransactionPool mTransactionPool; + private final Transitions mTransitions; + private final LegacySplitScreenController mSplitScreen; + private final LegacySplitScreenTaskListener mListener; + + private IBinder mPendingDismiss = null; + private boolean mDismissFromSnap = false; + private IBinder mPendingEnter = null; + private IBinder mAnimatingTransition = null; + + /** Keeps track of currently running animations */ + private final ArrayList<Animator> mAnimations = new ArrayList<>(); + + private Runnable mFinishCallback = null; + private SurfaceControl.Transaction mFinishTransaction; + + SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, + @NonNull LegacySplitScreenController splitScreen, + @NonNull LegacySplitScreenTaskListener listener) { + mTransactionPool = pool; + mTransitions = transitions; + mSplitScreen = splitScreen; + mListener = listener; + } + + @Override + public WindowContainerTransaction handleRequest(@WindowManager.TransitionType int type, + @NonNull IBinder transition, @Nullable ActivityManager.RunningTaskInfo triggerTask) { + WindowContainerTransaction out = null; + if (mSplitScreen.isDividerVisible()) { + // try to handle everything while in split-screen + out = new WindowContainerTransaction(); + if (triggerTask != null) { + final boolean shouldDismiss = + // if we close the primary-docked task, then leave split-screen since there + // is nothing behind it. + ((type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK) + && triggerTask.parentTaskId == mListener.mPrimary.taskId) + // if a non-resizable is launched, we also need to leave split-screen. + || ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) + && !triggerTask.isResizeable); + // In both cases, dismiss the primary + if (shouldDismiss) { + WindowManagerProxy.buildDismissSplit(out, mListener, + mSplitScreen.getSplitLayout(), true /* dismiss */); + if (type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) { + out.reorder(triggerTask.token, true /* onTop */); + } + mPendingDismiss = transition; + } + } + } else if (triggerTask != null) { + // Not in split mode, so look for an open with a trigger task. + if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) + && triggerTask.configuration.windowConfiguration.getWindowingMode() + == WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + out = new WindowContainerTransaction(); + mSplitScreen.prepareEnterSplitTransition(out); + mPendingEnter = transition; + } + } + return out; + } + + // TODO(shell-transitions): real animations + private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) { + final float end = show ? 1.f : 0.f; + final float start = 1.f - end; + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + final ValueAnimator va = ValueAnimator.ofFloat(start, end); + va.setDuration(500); + va.addUpdateListener(animation -> { + float fraction = animation.getAnimatedFraction(); + transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction); + transaction.apply(); + }); + final Runnable finisher = () -> { + transaction.setAlpha(leash, end); + transaction.apply(); + mTransactionPool.release(transaction); + mTransitions.getMainExecutor().execute(() -> { + mAnimations.remove(va); + onFinish(); + }); + }; + va.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { } + + @Override + public void onAnimationEnd(Animator animation) { + finisher.run(); + } + + @Override + public void onAnimationCancel(Animator animation) { + finisher.run(); + } + + @Override + public void onAnimationRepeat(Animator animation) { } + }); + mAnimations.add(va); + mTransitions.getAnimExecutor().execute(va::start); + } + + // TODO(shell-transitions): real animations + private void startExampleResizeAnimation(@NonNull SurfaceControl leash, + @NonNull Rect startBounds, @NonNull Rect endBounds) { + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f); + va.setDuration(500); + va.addUpdateListener(animation -> { + float fraction = animation.getAnimatedFraction(); + transaction.setWindowCrop(leash, + (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction), + (int) (startBounds.height() * (1.f - fraction) + + endBounds.height() * fraction)); + transaction.setPosition(leash, + startBounds.left * (1.f - fraction) + endBounds.left * fraction, + startBounds.top * (1.f - fraction) + endBounds.top * fraction); + transaction.apply(); + }); + final Runnable finisher = () -> { + transaction.setWindowCrop(leash, 0, 0); + transaction.setPosition(leash, endBounds.left, endBounds.top); + transaction.apply(); + mTransactionPool.release(transaction); + mTransitions.getMainExecutor().execute(() -> { + mAnimations.remove(va); + onFinish(); + }); + }; + va.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + finisher.run(); + } + + @Override + public void onAnimationCancel(Animator animation) { + finisher.run(); + } + }); + mAnimations.add(va); + mTransitions.getAnimExecutor().execute(va::start); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) { + if (transition != mPendingDismiss && transition != mPendingEnter) { + // If we're not in split-mode, just abort + if (!mSplitScreen.isDividerVisible()) return false; + // Check to see if HOME is involved + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() == null + || change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) continue; + if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) { + mSplitScreen.ensureMinimizedSplit(); + } else if (change.getMode() == TRANSIT_CLOSE + || change.getMode() == TRANSIT_TO_BACK) { + mSplitScreen.ensureNormalSplit(); + } + } + // Use normal animations. + return false; + } + + mFinishCallback = finishCallback; + mFinishTransaction = mTransactionPool.acquire(); + mAnimatingTransition = transition; + + // Play fade animations + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final SurfaceControl leash = change.getLeash(); + final int mode = info.getChanges().get(i).getMode(); + + if (mode == TRANSIT_CHANGE) { + if (change.getParent() != null) { + // This is probably reparented, so we want the parent to be immediately visible + final TransitionInfo.Change parentChange = info.getChange(change.getParent()); + t.show(parentChange.getLeash()); + t.setAlpha(parentChange.getLeash(), 1.f); + // and then animate this layer outside the parent (since, for example, this is + // the home task animating from fullscreen to part-screen). + t.reparent(leash, info.getRootLeash()); + t.setLayer(leash, info.getChanges().size() - i); + // build the finish reparent/reposition + mFinishTransaction.reparent(leash, parentChange.getLeash()); + mFinishTransaction.setPosition(leash, + change.getEndRelOffset().x, change.getEndRelOffset().y); + } + // TODO(shell-transitions): screenshot here + final Rect startBounds = new Rect(change.getStartAbsBounds()); + final boolean isHome = change.getTaskInfo() != null + && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME; + if (mPendingDismiss == transition && mDismissFromSnap && !isHome) { + // Home is special since it doesn't move during fling. Everything else, though, + // when dismissing from snap, the top/left is at 0,0. + startBounds.offsetTo(0, 0); + } + final Rect endBounds = new Rect(change.getEndAbsBounds()); + startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); + endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); + startExampleResizeAnimation(leash, startBounds, endBounds); + } + if (change.getParent() != null) { + continue; + } + + if (transition == mPendingEnter + && mListener.mPrimary.token.equals(change.getContainer()) + || mListener.mSecondary.token.equals(change.getContainer())) { + t.setWindowCrop(leash, change.getStartAbsBounds().width(), + change.getStartAbsBounds().height()); + if (mListener.mPrimary.token.equals(change.getContainer())) { + // Move layer to top since we want it above the oversized home task during + // animation even though home task is on top in hierarchy. + t.setLayer(leash, info.getChanges().size() + 1); + } + } + boolean isOpening = Transitions.isOpeningType(info.getType()); + if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { + // fade in + startExampleAnimation(leash, true /* show */); + } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { + // fade out + if (transition == mPendingDismiss && mDismissFromSnap) { + // Dismissing via snap-to-top/bottom means that the dismissed task is already + // not-visible (usually cropped to oblivion) so immediately set its alpha to 0 + // and don't animate it so it doesn't pop-in when reparented. + t.setAlpha(leash, 0.f); + } else { + startExampleAnimation(leash, false /* show */); + } + } + } + if (transition == mPendingEnter) { + // If entering, check if we should enter into minimized or normal split + boolean homeIsVisible = false; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() == null + || change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) { + continue; + } + homeIsVisible = change.getMode() == TRANSIT_OPEN + || change.getMode() == TRANSIT_TO_FRONT + || change.getMode() == TRANSIT_CHANGE; + break; + } + mSplitScreen.finishEnterSplitTransition(homeIsVisible); + } + t.apply(); + onFinish(); + return true; + } + + @ExternalThread + void dismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout, + boolean dismissOrMaximize, boolean snapped) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + WindowManagerProxy.buildDismissSplit(wct, tiles, layout, dismissOrMaximize); + mTransitions.getMainExecutor().execute(() -> { + mDismissFromSnap = snapped; + mPendingDismiss = mTransitions.startTransition(TRANSIT_SPLIT_DISMISS_SNAP, wct, this); + }); + } + + private void onFinish() { + if (!mAnimations.isEmpty()) return; + mFinishTransaction.apply(); + mTransactionPool.release(mFinishTransaction); + mFinishTransaction = null; + mFinishCallback.run(); + mFinishCallback = null; + if (mAnimatingTransition == mPendingEnter) { + mPendingEnter = null; + } + if (mAnimatingTransition == mPendingDismiss) { + mSplitScreen.onDismissSplit(); + mPendingDismiss = null; + } + mDismissFromSnap = false; + mAnimatingTransition = null; + } +} 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 68da35d9c4b1..ad05e6d3e6c4 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 @@ -40,6 +40,7 @@ import android.window.WindowOrganizer; import com.android.internal.annotations.GuardedBy; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.List; @@ -90,7 +91,11 @@ class WindowManagerProxy { void dismissOrMaximizeDocked(final LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout, final boolean dismissOrMaximize) { - mExecutor.execute(() -> applyDismissSplit(tiles, layout, dismissOrMaximize)); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + tiles.mSplitScreenController.startDismissSplit(!dismissOrMaximize, true /* snapped */); + } else { + mExecutor.execute(() -> applyDismissSplit(tiles, layout, dismissOrMaximize)); + } } public void setResizing(final boolean resizing) { @@ -181,6 +186,18 @@ class WindowManagerProxy { return isHomeResizable; } + /** @see #buildEnterSplit */ + boolean applyEnterSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout) { + // 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(); + final boolean isHomeResizable = buildEnterSplit(wct, tiles, layout); + applySyncTransaction(wct); + return isHomeResizable; + } + /** * Finishes entering split-screen by reparenting all FULLSCREEN tasks into the secondary split. * This assumes there is already something in the primary split since that is usually what @@ -189,14 +206,10 @@ class WindowManagerProxy { * * @return whether the home stack is resizable */ - boolean applyEnterSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout) { - // 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); + boolean buildEnterSplit(WindowContainerTransaction outWct, LegacySplitScreenTaskListener tiles, + LegacySplitDisplayLayout layout) { List<ActivityManager.RunningTaskInfo> rootTasks = mTaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */); - WindowContainerTransaction wct = new WindowContainerTransaction(); if (rootTasks.isEmpty()) { return false; } @@ -215,48 +228,60 @@ class WindowManagerProxy { // Since this iterates from bottom to top, update topHomeTask for every fullscreen task // so it will be left with the status of the top one. topHomeTask = isHomeOrRecentTask(rootTask) ? rootTask : null; - wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */); + outWct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */); } // Move the secondary split-forward. - wct.reorder(tiles.mSecondary.token, true /* onTop */); - boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct); - if (topHomeTask != null) { + outWct.reorder(tiles.mSecondary.token, true /* onTop */); + boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, + outWct); + if (topHomeTask != null && !Transitions.ENABLE_SHELL_TRANSITIONS) { // Translate/update-crop of secondary out-of-band with sync transaction -- Until BALST // is enabled, this temporarily syncs the home surface position with offset until // sync transaction finishes. - wct.setBoundsChangeTransaction(topHomeTask.token, tiles.mHomeBounds); + outWct.setBoundsChangeTransaction(topHomeTask.token, tiles.mHomeBounds); } - applySyncTransaction(wct); return isHomeResizable; } - boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) { + static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) { final int atype = ti.getActivityType(); return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS; } + /** @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(); + buildDismissSplit(wct, tiles, layout, dismissOrMaximize); + applySyncTransaction(wct); + } + /** * Reparents all tile members back to their display and resets home task override bounds. * @param dismissOrMaximize When {@code true} this resolves the split by closing the primary * split (thus resulting in the top of the secondary split becoming * fullscreen. {@code false} resolves the other way. */ - void applyDismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout, + static void buildDismissSplit(WindowContainerTransaction outWct, + 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 TaskOrganizer taskOrg = tiles.getTaskOrganizer(); List<ActivityManager.RunningTaskInfo> primaryChildren = - mTaskOrganizer.getChildTasks(tiles.mPrimary.token, null /* activityTypes */); + taskOrg.getChildTasks(tiles.mPrimary.token, null /* activityTypes */); List<ActivityManager.RunningTaskInfo> secondaryChildren = - mTaskOrganizer.getChildTasks(tiles.mSecondary.token, null /* activityTypes */); + taskOrg.getChildTasks(tiles.mSecondary.token, null /* activityTypes */); // In some cases (eg. non-resizable is launched), system-server will leave split-screen. // as a result, the above will not capture any tasks; yet, we need to clean-up the // home task bounds. List<ActivityManager.RunningTaskInfo> freeHomeAndRecents = - mTaskOrganizer.getRootTasks(DEFAULT_DISPLAY, HOME_AND_RECENTS); + taskOrg.getRootTasks(DEFAULT_DISPLAY, HOME_AND_RECENTS); // Filter out the root split tasks freeHomeAndRecents.removeIf(p -> p.token.equals(tiles.mSecondary.token) || p.token.equals(tiles.mPrimary.token)); @@ -265,11 +290,10 @@ class WindowManagerProxy { && freeHomeAndRecents.isEmpty()) { return; } - WindowContainerTransaction wct = new WindowContainerTransaction(); if (dismissOrMaximize) { // Dismissing, so move all primary split tasks first for (int i = primaryChildren.size() - 1; i >= 0; --i) { - wct.reparent(primaryChildren.get(i).token, null /* parent */, + outWct.reparent(primaryChildren.get(i).token, null /* parent */, true /* onTop */); } boolean homeOnTop = false; @@ -277,16 +301,16 @@ class WindowManagerProxy { // order within the secondary split. for (int i = secondaryChildren.size() - 1; i >= 0; --i) { final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i); - wct.reparent(ti.token, null /* parent */, true /* onTop */); + outWct.reparent(ti.token, null /* parent */, true /* onTop */); if (isHomeOrRecentTask(ti)) { - wct.setBounds(ti.token, null); - wct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED); + outWct.setBounds(ti.token, null); + outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED); if (i == 0) { homeOnTop = true; } } } - if (homeOnTop) { + if (homeOnTop && !Transitions.ENABLE_SHELL_TRANSITIONS) { // Translate/update-crop of secondary out-of-band with sync transaction -- instead // play this in sync with new home-app frame because until BALST is enabled this // shows up on screen before the syncTransaction returns. @@ -304,7 +328,7 @@ class WindowManagerProxy { layout.mDisplayLayout.height()); crop.offset(-posX, -posY); sft.setWindowCrop(tiles.mSecondarySurface, crop); - wct.setBoundsChangeTransaction(tiles.mSecondary.token, sft); + outWct.setBoundsChangeTransaction(tiles.mSecondary.token, sft); } } else { // Maximize, so move non-home secondary split first @@ -312,7 +336,7 @@ class WindowManagerProxy { if (isHomeOrRecentTask(secondaryChildren.get(i))) { continue; } - wct.reparent(secondaryChildren.get(i).token, null /* parent */, + outWct.reparent(secondaryChildren.get(i).token, null /* parent */, true /* onTop */); } // Find and place home tasks in-between. This simulates the fact that there was @@ -320,24 +344,23 @@ class WindowManagerProxy { for (int i = secondaryChildren.size() - 1; i >= 0; --i) { final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i); if (isHomeOrRecentTask(ti)) { - wct.reparent(ti.token, null /* parent */, true /* onTop */); + outWct.reparent(ti.token, null /* parent */, true /* onTop */); // reset bounds and mode too - wct.setBounds(ti.token, null); - wct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED); + outWct.setBounds(ti.token, null); + outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED); } } for (int i = primaryChildren.size() - 1; i >= 0; --i) { - wct.reparent(primaryChildren.get(i).token, null /* parent */, + outWct.reparent(primaryChildren.get(i).token, null /* parent */, true /* onTop */); } } for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) { - wct.setBounds(freeHomeAndRecents.get(i).token, null); - wct.setWindowingMode(freeHomeAndRecents.get(i).token, WINDOWING_MODE_UNDEFINED); + outWct.setBounds(freeHomeAndRecents.get(i).token, null); + outWct.setWindowingMode(freeHomeAndRecents.get(i).token, WINDOWING_MODE_UNDEFINED); } // Reset focusable to true - wct.setFocusable(tiles.mPrimary.token, true /* focusable */); - applySyncTransaction(wct); + outWct.setFocusable(tiles.mPrimary.token, true /* focusable */); } /** 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 963909621a1b..146f231a8854 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 @@ -203,7 +203,7 @@ public class OneHandedAnimationController { mSurfaceTransactionHelper = helper; } - OneHandedTransitionAnimator<T> setOneHandedAnimationCallbacks( + OneHandedTransitionAnimator<T> addOneHandedAnimationCallback( OneHandedAnimationCallback callback) { mOneHandedAnimationCallbacks.add(callback); return this; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java new file mode 100644 index 000000000000..a74f4761af0c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.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 com.android.wm.shell.onehanded; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Handler; +import android.util.Log; +import android.view.SurfaceControl; +import android.view.SurfaceSession; +import android.window.DisplayAreaAppearedInfo; +import android.window.DisplayAreaInfo; +import android.window.DisplayAreaOrganizer; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.android.internal.annotations.GuardedBy; +import com.android.wm.shell.R; +import com.android.wm.shell.common.DisplayController; + +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Manages OneHanded color background layer areas. + * To avoid when turning the Dark theme on, users can not clearly identify + * the screen has entered one handed mode. + */ +public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer + implements OneHandedTransitionCallback { + private static final String TAG = "OneHandedBackgroundPanelOrganizer"; + + private final Object mLock = new Object(); + private final SurfaceSession mSurfaceSession = new SurfaceSession(); + private final float[] mColor; + private final float mAlpha; + private final Rect mRect; + private final Handler mHandler; + private final Point mDisplaySize = new Point(); + private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory + mSurfaceControlTransactionFactory; + + @VisibleForTesting + @GuardedBy("mLock") + boolean mIsShowing; + @Nullable + @GuardedBy("mLock") + private SurfaceControl mBackgroundSurface; + @Nullable + @GuardedBy("mLock") + private SurfaceControl mParentLeash; + + private final OneHandedAnimationCallback mOneHandedAnimationCallback = + new OneHandedAnimationCallback() { + @Override + public void onOneHandedAnimationStart( + OneHandedAnimationController.OneHandedTransitionAnimator animator) { + mHandler.post(() -> showBackgroundPanelLayer()); + } + }; + + @Override + public void onStopFinished(Rect bounds) { + mHandler.post(() -> removeBackgroundPanelLayer()); + } + + public OneHandedBackgroundPanelOrganizer(Context context, DisplayController displayController, + Executor executor) { + super(executor); + displayController.getDisplay(DEFAULT_DISPLAY).getRealSize(mDisplaySize); + final Resources res = context.getResources(); + final float defaultRGB = res.getFloat(R.dimen.config_one_handed_background_rgb); + mColor = new float[]{defaultRGB, defaultRGB, defaultRGB}; + mAlpha = res.getFloat(R.dimen.config_one_handed_background_alpha); + mRect = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y); + mHandler = new Handler(); + mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; + } + + @Override + public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo, + @NonNull SurfaceControl leash) { + synchronized (mLock) { + if (mParentLeash == null) { + mParentLeash = leash; + } else { + throw new RuntimeException("There should be only one DisplayArea for " + + "the one-handed mode background panel"); + } + } + } + + OneHandedAnimationCallback getOneHandedAnimationCallback() { + return mOneHandedAnimationCallback; + } + + @Override + public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) { + synchronized (mLock) { + final List<DisplayAreaAppearedInfo> displayAreaInfos; + displayAreaInfos = super.registerOrganizer(displayAreaFeature); + for (int i = 0; i < displayAreaInfos.size(); i++) { + final DisplayAreaAppearedInfo info = displayAreaInfos.get(i); + onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash()); + } + return displayAreaInfos; + } + } + + @Override + public void unregisterOrganizer() { + synchronized (mLock) { + super.unregisterOrganizer(); + mParentLeash = null; + } + } + + @Nullable + @VisibleForTesting + SurfaceControl getBackgroundSurface() { + synchronized (mLock) { + if (mParentLeash == null) { + return null; + } + + if (mBackgroundSurface == null) { + mBackgroundSurface = new SurfaceControl.Builder(mSurfaceSession) + .setParent(mParentLeash) + .setColorLayer() + .setFormat(PixelFormat.RGBA_8888) + .setOpaque(false) + .setName("one-handed-background-panel") + .setCallsite("OneHandedBackgroundPanelOrganizer") + .build(); + } + return mBackgroundSurface; + } + } + + @VisibleForTesting + void showBackgroundPanelLayer() { + synchronized (mLock) { + if (mIsShowing) { + return; + } + + if (getBackgroundSurface() == null) { + Log.w(TAG, "mBackgroundSurface is null !"); + return; + } + + SurfaceControl.Transaction transaction = + mSurfaceControlTransactionFactory.getTransaction(); + transaction.setLayer(mBackgroundSurface, -1 /* at bottom-most layer */) + .setColor(mBackgroundSurface, mColor) + .setAlpha(mBackgroundSurface, mAlpha) + .show(mBackgroundSurface) + .apply(); + transaction.close(); + mIsShowing = true; + } + } + + @VisibleForTesting + void removeBackgroundPanelLayer() { + synchronized (mLock) { + if (mBackgroundSurface == null) { + return; + } + + SurfaceControl.Transaction transaction = + mSurfaceControlTransactionFactory.getTransaction(); + transaction.remove(mBackgroundSurface); + transaction.apply(); + transaction.close(); + mBackgroundSurface = null; + mIsShowing = false; + } + } +} 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 69d8db20d110..00605d872e39 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 @@ -77,6 +77,7 @@ public class OneHandedController implements OneHanded { private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer; private final AccessibilityManager mAccessibilityManager; + private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer; /** * Handle rotation based on OnDisplayChangingListener callback @@ -204,17 +205,22 @@ public class OneHandedController implements OneHanded { OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(); OneHandedGestureHandler gestureHandler = new OneHandedGestureHandler( context, displayController); + OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer = + new OneHandedBackgroundPanelOrganizer(context, displayController, executor); OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer( - context, displayController, animationController, tutorialHandler, executor); + context, displayController, animationController, tutorialHandler, executor, + oneHandedBackgroundPanelOrganizer); IOverlayManager overlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); - return new OneHandedController(context, displayController, organizer, touchHandler, - tutorialHandler, gestureHandler, overlayManager, taskStackListener); + return new OneHandedController(context, displayController, + oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler, + gestureHandler, overlayManager, taskStackListener); } @VisibleForTesting OneHandedController(Context context, DisplayController displayController, + OneHandedBackgroundPanelOrganizer backgroundPanelOrganizer, OneHandedDisplayAreaOrganizer displayAreaOrganizer, OneHandedTouchHandler touchHandler, OneHandedTutorialHandler tutorialHandler, @@ -222,6 +228,7 @@ public class OneHandedController implements OneHanded { IOverlayManager overlayManager, TaskStackListenerImpl taskStackListener) { mContext = context; + mBackgroundPanelOrganizer = backgroundPanelOrganizer; mDisplayAreaOrganizer = displayAreaOrganizer; mDisplayController = displayController; mTouchHandler = touchHandler; @@ -355,6 +362,7 @@ public class OneHandedController implements OneHanded { mDisplayAreaOrganizer.registerTransitionCallback(mTouchHandler); mDisplayAreaOrganizer.registerTransitionCallback(mGestureHandler); mDisplayAreaOrganizer.registerTransitionCallback(mTutorialHandler); + mDisplayAreaOrganizer.registerTransitionCallback(mBackgroundPanelOrganizer); } private void setupSettingObservers() { @@ -405,9 +413,12 @@ public class OneHandedController implements OneHanded { } // TODO Be aware to unregisterOrganizer() after animation finished mDisplayAreaOrganizer.unregisterOrganizer(); + mBackgroundPanelOrganizer.unregisterOrganizer(); if (mIsOneHandedEnabled) { mDisplayAreaOrganizer.registerOrganizer( OneHandedDisplayAreaOrganizer.FEATURE_ONE_HANDED); + mBackgroundPanelOrganizer.registerOrganizer( + OneHandedBackgroundPanelOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL); } mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled); mGestureHandler.onOneHandedEnabled(mIsOneHandedEnabled || mIsSwipeToNotificationEnabled); 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 7fb1faa60a6f..7873318fc82d 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 @@ -86,6 +86,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { mSurfaceControlTransactionFactory; private OneHandedTutorialHandler mTutorialHandler; private List<OneHandedTransitionCallback> mTransitionCallbacks = new ArrayList<>(); + private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer; @VisibleForTesting OneHandedAnimationCallback mOneHandedAnimationCallback = @@ -152,7 +153,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { public OneHandedDisplayAreaOrganizer(Context context, DisplayController displayController, OneHandedAnimationController animationController, - OneHandedTutorialHandler tutorialHandler, Executor executor) { + OneHandedTutorialHandler tutorialHandler, Executor executor, + OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer) { super(executor); mUpdateHandler = new Handler(OneHandedThread.get().getLooper(), mUpdateCallback); mAnimationController = animationController; @@ -166,6 +168,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { animationDurationConfig); mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; mTutorialHandler = tutorialHandler; + mBackgroundPanelOrganizer = oneHandedBackgroundGradientOrganizer; } @Override @@ -300,8 +303,10 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { mAnimationController.getAnimator(leash, fromBounds, toBounds); if (animator != null) { animator.setTransitionDirection(direction) - .setOneHandedAnimationCallbacks(mOneHandedAnimationCallback) - .setOneHandedAnimationCallbacks(mTutorialHandler.getAnimationCallback()) + .addOneHandedAnimationCallback(mOneHandedAnimationCallback) + .addOneHandedAnimationCallback(mTutorialHandler.getAnimationCallback()) + .addOneHandedAnimationCallback( + mBackgroundPanelOrganizer.getOneHandedAnimationCallback()) .setDuration(durationMs) .start(); } 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 951a68884e11..aa4ec1788a88 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 @@ -88,7 +88,7 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback, mDisplayController = displayController; displayController.addDisplayChangingController(this); mNavGestureHeight = context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.navigation_bar_gesture_height); + com.android.internal.R.dimen.navigation_bar_gesture_larger_height); mDragDistThreshold = context.getResources().getDimensionPixelSize( R.dimen.gestures_onehanded_drag_threshold); final float slop = ViewConfiguration.get(context).getScaledTouchSlop(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java index d59aec2cc446..8f8ec475a85c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java @@ -38,11 +38,11 @@ import java.util.ArrayList; public class PinnedStackListenerForwarder { private final IPinnedStackListener mListenerImpl = new PinnedStackListenerImpl(); - private final ShellExecutor mShellMainExecutor; + private final ShellExecutor mMainExecutor; private final ArrayList<PinnedStackListener> mListeners = new ArrayList<>(); - public PinnedStackListenerForwarder(ShellExecutor shellMainExecutor) { - mShellMainExecutor = shellMainExecutor; + public PinnedStackListenerForwarder(ShellExecutor mainExecutor) { + mMainExecutor = mainExecutor; } /** Adds a listener to receive updates from the WindowManagerService. */ @@ -94,35 +94,35 @@ public class PinnedStackListenerForwarder { private class PinnedStackListenerImpl extends IPinnedStackListener.Stub { @Override public void onMovementBoundsChanged(boolean fromImeAdjustment) { - mShellMainExecutor.execute(() -> { + mMainExecutor.execute(() -> { PinnedStackListenerForwarder.this.onMovementBoundsChanged(fromImeAdjustment); }); } @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { - mShellMainExecutor.execute(() -> { + mMainExecutor.execute(() -> { PinnedStackListenerForwarder.this.onImeVisibilityChanged(imeVisible, imeHeight); }); } @Override public void onActionsChanged(ParceledListSlice<RemoteAction> actions) { - mShellMainExecutor.execute(() -> { + mMainExecutor.execute(() -> { PinnedStackListenerForwarder.this.onActionsChanged(actions); }); } @Override public void onActivityHidden(ComponentName componentName) { - mShellMainExecutor.execute(() -> { + mMainExecutor.execute(() -> { PinnedStackListenerForwarder.this.onActivityHidden(componentName); }); } @Override public void onAspectRatioChanged(float aspectRatio) { - mShellMainExecutor.execute(() -> { + mMainExecutor.execute(() -> { PinnedStackListenerForwarder.this.onAspectRatioChanged(aspectRatio); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index e32d3b955081..df7c75332675 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -83,8 +83,18 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = ThreadLocal.withInitial(() -> { - FrameCallbackScheduler scheduler = runnable -> + final Looper initialLooper = Looper.myLooper(); + final FrameCallbackScheduler scheduler = new FrameCallbackScheduler() { + @Override + public void postFrameCallback(@androidx.annotation.NonNull Runnable runnable) { Choreographer.getSfInstance().postFrameCallback(t -> runnable.run()); + } + + @Override + public boolean isCurrentThread() { + return Looper.myLooper() == initialLooper; + } + }; AnimationHandler handler = new AnimationHandler(scheduler); return handler; }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 33439a412b4d..6b6bf5e4a776 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -155,7 +155,7 @@ public class PipTouchHandler { PipTaskOrganizer pipTaskOrganizer, FloatingContentCoordinator floatingContentCoordinator, PipUiEventLogger pipUiEventLogger, - ShellExecutor shellMainExecutor) { + ShellExecutor mainExecutor) { // Initialize the Pip input consumer mContext = context; mAccessibilityManager = context.getSystemService(AccessibilityManager.class); @@ -186,7 +186,7 @@ public class PipTouchHandler { mFloatingContentCoordinator = floatingContentCoordinator; mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState, mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(), - this::onAccessibilityShowMenu, this::updateMovementBounds, shellMainExecutor); + this::onAccessibilityShowMenu, this::updateMovementBounds, mainExecutor); mPipUiEventLogger = pipUiEventLogger; 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 0955056900f1..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java +++ /dev/null @@ -1,542 +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 { - 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.attachPipController(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). - */ - 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); - } - - /** - * Moves the PIPed activity to the fullscreen and closes PIP system UI. - */ - 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/PipControlsView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsView.java deleted file mode 100644 index 95d9b77c513e..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsView.java +++ /dev/null @@ -1,61 +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 android.content.Context; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.widget.LinearLayout; - -import com.android.wm.shell.R; - - -/** - * A view containing PIP controls including fullscreen, close, and media controls. - */ -public class PipControlsView extends LinearLayout { - - public PipControlsView(Context context) { - this(context, null); - } - - public PipControlsView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - layoutInflater.inflate(R.layout.tv_pip_controls, this); - setOrientation(LinearLayout.HORIZONTAL); - setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); - } - - PipControlButtonView getFullscreenButton() { - return findViewById(R.id.full_button); - } - - PipControlButtonView getCloseButton() { - return findViewById(R.id.close_button); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsViewController.java deleted file mode 100644 index 5265e7705ed9..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsViewController.java +++ /dev/null @@ -1,158 +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 android.app.PendingIntent; -import android.app.RemoteAction; -import android.content.Context; -import android.graphics.Color; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; - -import com.android.wm.shell.R; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -/** - * Controller for {@link PipControlsView}. - */ -public class PipControlsViewController { - private static final String TAG = PipControlsViewController.class.getSimpleName(); - - private static final float DISABLED_ACTION_ALPHA = 0.54f; - - private final PipController mPipController; - - private final Context mContext; - private final Handler mUiThreadHandler; - private final PipControlsView mView; - private final List<PipControlButtonView> mAdditionalButtons = new ArrayList<>(); - - private final List<RemoteAction> mCustomActions = new ArrayList<>(); - private final List<RemoteAction> mMediaActions = new ArrayList<>(); - - public PipControlsViewController(PipControlsView view, PipController pipController) { - mContext = view.getContext(); - mUiThreadHandler = new Handler(Looper.getMainLooper()); - mPipController = pipController; - mView = view; - - mView.getFullscreenButton().setOnClickListener(v -> mPipController.movePipToFullscreen()); - mView.getCloseButton().setOnClickListener(v -> mPipController.closePip()); - - mPipController.getPipMediaController().addActionListener(this::onMediaActionsChanged); - } - - PipControlsView getView() { - return mView; - } - - /** - * Updates the set of activity-defined actions. - */ - void setCustomActions(List<? extends RemoteAction> actions) { - if (mCustomActions.isEmpty() && actions.isEmpty()) { - // Nothing changed - return early. - return; - } - mCustomActions.clear(); - mCustomActions.addAll(actions); - updateAdditionalActions(); - } - - private void onMediaActionsChanged(List<RemoteAction> actions) { - if (mMediaActions.isEmpty() && actions.isEmpty()) { - // Nothing changed - return early. - return; - } - mMediaActions.clear(); - mMediaActions.addAll(actions); - - // Update the view only if there are no custom actions (media actions are only shown when - // there no custom actions). - if (mCustomActions.isEmpty()) { - updateAdditionalActions(); - } - } - - private void updateAdditionalActions() { - final List<RemoteAction> actionsToDisplay; - if (!mCustomActions.isEmpty()) { - // If there are custom actions: show them. - actionsToDisplay = mCustomActions; - } else if (!mMediaActions.isEmpty()) { - // If there are no custom actions, but there media actions: show them. - actionsToDisplay = mMediaActions; - } else { - // If there no custom actions and no media actions: clean up all the additional buttons. - actionsToDisplay = Collections.emptyList(); - } - - // Make sure we exactly as many additional buttons as we have actions to display. - final int actionsNumber = actionsToDisplay.size(); - int buttonsNumber = mAdditionalButtons.size(); - if (actionsNumber > buttonsNumber) { - final LayoutInflater layoutInflater = LayoutInflater.from(mContext); - // Add buttons until we have enough to display all of the actions. - while (actionsNumber > buttonsNumber) { - final PipControlButtonView button = (PipControlButtonView) layoutInflater.inflate( - R.layout.tv_pip_custom_control, mView, false); - mView.addView(button); - mAdditionalButtons.add(button); - - buttonsNumber++; - } - } else if (actionsNumber < buttonsNumber) { - // Hide buttons until we as many as the actions. - while (actionsNumber < buttonsNumber) { - final View button = mAdditionalButtons.get(buttonsNumber - 1); - button.setVisibility(View.GONE); - button.setOnClickListener(null); - - buttonsNumber--; - } - } - - // "Assign" actions to the buttons. - for (int index = 0; index < actionsNumber; index++) { - final RemoteAction action = actionsToDisplay.get(index); - final PipControlButtonView button = mAdditionalButtons.get(index); - button.setVisibility(View.VISIBLE); // Ensure the button is visible. - button.setText(action.getContentDescription()); - button.setEnabled(action.isEnabled()); - button.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); - button.setOnClickListener(v -> { - try { - action.getActionIntent().send(); - } catch (PendingIntent.CanceledException e) { - Log.w(TAG, "Failed to send action", e); - } - }); - - action.getIcon().loadDrawableAsync(mContext, drawable -> { - drawable.setTint(Color.WHITE); - button.setImageDrawable(drawable); - }, mUiThreadHandler); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuView.java deleted file mode 100644 index 83cb7ce8065b..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuView.java +++ /dev/null @@ -1,126 +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.view.KeyEvent.ACTION_UP; -import static android.view.KeyEvent.KEYCODE_BACK; - -import android.animation.Animator; -import android.animation.AnimatorInflater; -import android.annotation.Nullable; -import android.app.RemoteAction; -import android.content.Context; -import android.content.pm.ParceledListSlice; -import android.util.Log; -import android.view.KeyEvent; -import android.view.SurfaceControl; -import android.view.ViewRootImpl; -import android.view.WindowManagerGlobal; -import android.widget.FrameLayout; - -import com.android.wm.shell.R; - -import java.util.Collections; - -/** - * The Menu View that shows controls of the PiP. Always fullscreen. - */ -public class PipMenuView extends FrameLayout { - private static final String TAG = "PipMenuView"; - private static final boolean DEBUG = PipController.DEBUG; - - private final Animator mFadeInAnimation; - private final Animator mFadeOutAnimation; - private final PipControlsViewController mPipControlsViewController; - @Nullable - private OnBackPressListener mOnBackPressListener; - - public PipMenuView(Context context, PipController pipController) { - super(context, null, 0); - inflate(context, R.layout.tv_pip_menu, this); - - mPipControlsViewController = new PipControlsViewController( - findViewById(R.id.pip_controls), pipController); - mFadeInAnimation = AnimatorInflater.loadAnimator( - mContext, R.anim.tv_pip_menu_fade_in_animation); - mFadeInAnimation.setTarget(mPipControlsViewController.getView()); - mFadeOutAnimation = AnimatorInflater.loadAnimator( - mContext, R.anim.tv_pip_menu_fade_out_animation); - mFadeOutAnimation.setTarget(mPipControlsViewController.getView()); - } - - @Nullable - SurfaceControl getWindowSurfaceControl() { - final ViewRootImpl root = getViewRootImpl(); - if (root == null) { - return null; - } - final SurfaceControl out = root.getSurfaceControl(); - if (out != null && out.isValid()) { - return out; - } - return null; - } - - void showMenu() { - mFadeInAnimation.start(); - setAlpha(1.0f); - grantWindowFocus(true); - } - - void hideMenu() { - mFadeOutAnimation.start(); - setAlpha(0.0f); - grantWindowFocus(false); - } - - private void grantWindowFocus(boolean grantFocus) { - try { - WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */, - getViewRootImpl().getInputToken(), grantFocus); - } catch (Exception e) { - Log.e(TAG, "Unable to update focus as menu disappears", e); - } - } - - void setOnBackPressListener(OnBackPressListener onBackPressListener) { - mOnBackPressListener = onBackPressListener; - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - if (event.getKeyCode() == KEYCODE_BACK && event.getAction() == ACTION_UP - && mOnBackPressListener != null) { - mOnBackPressListener.onBackPress(); - return true; - } else { - return super.dispatchKeyEvent(event); - } - } - - void setAppActions(ParceledListSlice<RemoteAction> actions) { - if (DEBUG) Log.d(TAG, "onPipMenuActionsChanged()"); - - boolean hasCustomActions = actions != null && !actions.getList().isEmpty(); - mPipControlsViewController.setCustomActions( - hasCustomActions ? actions.getList() : Collections.emptyList()); - } - - interface OnBackPressListener { - void onBackPress(); - } -} 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 000000000000..8bc60f9dbcd9 --- /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/PipControlButtonView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java index 4e82bb557fb9..6f7cd82f8da0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlButtonView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java @@ -31,64 +31,51 @@ import android.widget.TextView; import com.android.wm.shell.R; /** - * A view containing PIP controls including fullscreen, close, and media controls. + * A View that represents Pip Menu action button, such as "Fullscreen" and "Close" as well custom + * (provided by the application in Pip) and media buttons. */ -public class PipControlButtonView extends RelativeLayout { - - private OnFocusChangeListener mFocusChangeListener; - private ImageView mIconImageView; - ImageView mButtonImageView; - private TextView mDescriptionTextView; +public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener { + private final ImageView mIconImageView; + private final ImageView mButtonImageView; + private final TextView mDescriptionTextView; private Animator mTextFocusGainAnimator; private Animator mButtonFocusGainAnimator; private Animator mTextFocusLossAnimator; private Animator mButtonFocusLossAnimator; + private OnClickListener mOnClickListener; - private final OnFocusChangeListener mInternalFocusChangeListener = - new OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - startFocusGainAnimation(); - } else { - startFocusLossAnimation(); - } - - if (mFocusChangeListener != null) { - mFocusChangeListener.onFocusChange(PipControlButtonView.this, hasFocus); - } - } - }; - - public PipControlButtonView(Context context) { + public TvPipMenuActionButton(Context context) { this(context, null, 0, 0); } - public PipControlButtonView(Context context, AttributeSet attrs) { + public TvPipMenuActionButton(Context context, AttributeSet attrs) { this(context, attrs, 0, 0); } - public PipControlButtonView(Context context, AttributeSet attrs, int defStyleAttr) { + public TvPipMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } - public PipControlButtonView( + public TvPipMenuActionButton( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - LayoutInflater inflater = (LayoutInflater) getContext() + final LayoutInflater inflater = (LayoutInflater) getContext() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.tv_pip_control_button, this); + inflater.inflate(R.layout.tv_pip_menu_action_button, this); mIconImageView = findViewById(R.id.icon); mButtonImageView = findViewById(R.id.button); mDescriptionTextView = findViewById(R.id.desc); - int[] values = new int[]{android.R.attr.src, android.R.attr.text}; - TypedArray typedArray = context.obtainStyledAttributes(attrs, values, defStyleAttr, + final int[] values = new int[]{android.R.attr.src, android.R.attr.text}; + final TypedArray typedArray = context.obtainStyledAttributes(attrs, values, defStyleAttr, defStyleRes); setImageResource(typedArray.getResourceId(0, 0)); - setText(typedArray.getResourceId(1, 0)); + final int textResId = typedArray.getResourceId(1, 0); + if (textResId != 0) { + setTextAndDescription(getContext().getString(textResId)); + } typedArray.recycle(); } @@ -96,7 +83,13 @@ public class PipControlButtonView extends RelativeLayout { @Override public void onFinishInflate() { super.onFinishInflate(); - mButtonImageView.setOnFocusChangeListener(mInternalFocusChangeListener); + mButtonImageView.setOnFocusChangeListener((v, hasFocus) -> { + if (hasFocus) { + startFocusGainAnimation(); + } else { + startFocusLossAnimation(); + } + }); mTextFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(), R.anim.tv_pip_controls_focus_gain_animation); @@ -115,12 +108,19 @@ public class PipControlButtonView extends RelativeLayout { @Override public void setOnClickListener(OnClickListener listener) { - mButtonImageView.setOnClickListener(listener); + // We do not want to set an OnClickListener to the TvPipMenuActionButton itself, but only to + // the ImageView. So let's "cash" the listener we've been passed here and set a "proxy" + // listener to the ImageView. + mOnClickListener = listener; + mButtonImageView.setOnClickListener(listener != null ? this : null); } @Override - public void setOnFocusChangeListener(OnFocusChangeListener listener) { - mFocusChangeListener = listener; + public void onClick(View v) { + if (mOnClickListener != null) { + // Pass the correct view - this. + mOnClickListener.onClick(this); + } } /** @@ -142,21 +142,11 @@ public class PipControlButtonView extends RelativeLayout { /** * Sets the text for description the with the given string. */ - public void setText(CharSequence text) { + public void setTextAndDescription(CharSequence text) { mButtonImageView.setContentDescription(text); mDescriptionTextView.setText(text); } - /** - * Sets the text for description the with the given resource id. - */ - public void setText(int resId) { - if (resId != 0) { - mButtonImageView.setContentDescription(getContext().getString(resId)); - mDescriptionTextView.setText(resId); - } - } - private static void cancelAnimator(Animator animator) { if (animator.isStarted()) { animator.cancel(); @@ -187,8 +177,8 @@ public class PipControlButtonView extends RelativeLayout { mTextFocusLossAnimator.start(); if (mButtonImageView.hasFocus()) { // Button uses ripple that has the default animation for the focus changes. - // Howevever, it doesn't expose the API to fade out while it is focused, - // so we should manually run the fade out animation when PIP controls row loses focus. + // However, it doesn't expose the API to fade out while it is focused, so we should + // manually run the fade out animation when PIP controls row loses focus. mButtonFocusLossAnimator.start(); } } 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 5d0d761abd93..470ab0c9c0e3 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 @@ -19,38 +19,97 @@ package com.android.wm.shell.pip.tv; import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP; import android.app.RemoteAction; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ParceledListSlice; import android.util.Log; import android.view.SurfaceControl; +import androidx.annotation.Nullable; + import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.pip.PipBoundsState; +import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipMenuController; +import java.util.ArrayList; +import java.util.List; + /** * Manages the visibility of the PiP Menu as user interacts with PiP. */ -public class TvPipMenuController implements PipMenuController { +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; private final PipBoundsState mPipBoundsState; - private PipMenuView mMenuView; - private PipController mPipController; + + private Delegate mDelegate; private SurfaceControl mLeash; + private TvPipMenuView mMenuView; + + private final List<RemoteAction> mMediaActions = new ArrayList<>(); + private final List<RemoteAction> mAppActions = new ArrayList<>(); public TvPipMenuController(Context context, PipBoundsState pipBoundsState, - SystemWindows systemWindows) { + SystemWindows systemWindows, PipMediaController pipMediaController) { mContext = context; mPipBoundsState = pipBoundsState; mSystemWindows = systemWindows; + + // We need to "close" the menu the platform call for all the system dialogs to close (for + // example, on the Home button press). + final BroadcastReceiver closeSystemDialogsBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + hideMenu(); + } + }; + context.registerReceiver(closeSystemDialogsBroadcastReceiver, + new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + + pipMediaController.addActionListener(this::onMediaActionsChanged); + } + + 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; + } + + @Override + public void attach(SurfaceControl leash) { + if (mDelegate == null) { + throw new IllegalStateException("Delegate is not set."); + } + + mLeash = leash; + attachPipMenuView(); } - void attachPipController(PipController pipController) { - mPipController = pipController; + private void attachPipMenuView() { + if (DEBUG) Log.d(TAG, "attachPipMenuView()"); + + if (mMenuView != null) { + detachPipMenuView(); + } + + mMenuView = new TvPipMenuView(mContext); + mMenuView.setListener(this); + mSystemWindows.addView(mMenuView, + getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), + 0, SHELL_ROOT_LAYER_PIP); } @Override @@ -61,7 +120,8 @@ public class TvPipMenuController implements PipMenuController { mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE, mPipBoundsState.getDisplayBounds().width(), mPipBoundsState.getDisplayBounds().height())); - mMenuView.showMenu(); + maybeUpdateMenuViewActions(); + mMenuView.show(); // By default, SystemWindows views are above everything else. // Set the relative z-order so the menu is below PiP. @@ -74,18 +134,20 @@ public class TvPipMenuController implements PipMenuController { } void hideMenu() { - if (DEBUG) Log.d(TAG, "hideMenu()"); + hideMenu(true); + } + + void hideMenu(boolean movePipWindow) { + if (DEBUG) Log.d(TAG, "hideMenu(), movePipWindow=" + movePipWindow); - if (isMenuVisible()) { - mMenuView.hideMenu(); - mPipController.resizePinnedStack(PipController.STATE_PIP); + if (!isMenuVisible()) { + return; } - } - @Override - public void attach(SurfaceControl leash) { - mLeash = leash; - attachPipMenuView(); + mMenuView.hide(); + if (movePipWindow) { + mDelegate.movePipToNormalPosition(); + } } @Override @@ -95,20 +157,6 @@ public class TvPipMenuController implements PipMenuController { mLeash = null; } - private void attachPipMenuView() { - if (DEBUG) Log.d(TAG, "attachPipMenuView()"); - - if (mMenuView != null) { - detachPipMenuView(); - } - - mMenuView = new PipMenuView(mContext, mPipController); - mMenuView.setOnBackPressListener(this::hideMenu); - mSystemWindows.addView(mMenuView, - getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), - 0, SHELL_ROOT_LAYER_PIP); - } - private void detachPipMenuView() { if (DEBUG) Log.d(TAG, "detachPipMenuView()"); @@ -121,18 +169,65 @@ public class TvPipMenuController implements PipMenuController { } @Override - public void setAppActions(ParceledListSlice<RemoteAction> appActions) { - if (DEBUG) Log.d(TAG, "setAppActions(), actions=" + appActions); + public void setAppActions(ParceledListSlice<RemoteAction> actions) { + if (DEBUG) Log.d(TAG, "setAppActions()"); + updateAdditionalActionsList(mAppActions, actions.getList()); + } - if (mMenuView != null) { - mMenuView.setAppActions(appActions); + private void onMediaActionsChanged(List<RemoteAction> actions) { + if (DEBUG) Log.d(TAG, "onMediaActionsChanged()"); + updateAdditionalActionsList(mMediaActions, actions); + } + + private void updateAdditionalActionsList( + List<RemoteAction> destination, @Nullable List<RemoteAction> source) { + final int number = source != null ? source.size() : 0; + if (number == 0 && destination.isEmpty()) { + // Nothing changed. + return; + } + + destination.clear(); + if (number > 0) { + destination.addAll(source); + } + maybeUpdateMenuViewActions(); + } + + private void maybeUpdateMenuViewActions() { + if (mMenuView == null) { + return; + } + if (!mAppActions.isEmpty()) { + mMenuView.setAdditionalActions(mAppActions); } else { - Log.w(TAG, "Cannot set remote actions, there is no View"); + mMenuView.setAdditionalActions(mMediaActions); } } @Override public boolean isMenuVisible() { - return mMenuView != null && mMenuView.getAlpha() == 1.0f; + return mMenuView != null && mMenuView.isVisible(); + } + + @Override + public void onBackPress() { + hideMenu(); + } + + @Override + public void onCloseButtonClick() { + mDelegate.closePip(); + } + + @Override + public void onFullscreenButtonClick() { + mDelegate.movePipToFullscreen(); + } + + interface Delegate { + void movePipToNormalPosition(); + void movePipToFullscreen(); + void closePip(); } } 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 new file mode 100644 index 000000000000..e08ca52fd2ca --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java @@ -0,0 +1,235 @@ +/* + * 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.animation.AnimatorInflater.loadAnimator; +import static android.view.KeyEvent.ACTION_UP; +import static android.view.KeyEvent.KEYCODE_BACK; + +import android.animation.Animator; +import android.app.PendingIntent; +import android.app.RemoteAction; +import android.content.Context; +import android.graphics.Color; +import android.os.Handler; +import android.os.Looper; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewRootImpl; +import android.view.WindowManagerGlobal; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.wm.shell.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * A View that represents Pip Menu on TV. It's responsible for displaying 2 ever-present Pip Menu + * actions: Fullscreen and Close, but could also display "additional" actions, that may be set via + * a {@link #setAdditionalActions(List)} call. + */ +public class TvPipMenuView extends FrameLayout implements View.OnClickListener { + private static final String TAG = "TvPipMenuView"; + private static final boolean DEBUG = TvPipController.DEBUG; + + private static final float DISABLED_ACTION_ALPHA = 0.54f; + + private final Handler mUiThreadHandler; + private final Animator mFadeInAnimation; + private final Animator mFadeOutAnimation; + @Nullable private Listener mListener; + + private final LinearLayout mActionButtonsContainer; + private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>(); + + public TvPipMenuView(@NonNull Context context) { + this(context, null); + } + + public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mUiThreadHandler = new Handler(Looper.getMainLooper()); + + inflate(context, R.layout.tv_pip_menu, this); + + mActionButtonsContainer = findViewById(R.id.tv_pip_menu_action_buttons); + mActionButtonsContainer.findViewById(R.id.tv_pip_menu_fullscreen_button) + .setOnClickListener(this); + mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button) + .setOnClickListener(this); + + mFadeInAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_in_animation); + mFadeInAnimation.setTarget(mActionButtonsContainer); + + mFadeOutAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_out_animation); + mFadeOutAnimation.setTarget(mActionButtonsContainer); + } + + void setListener(@Nullable Listener listener) { + mListener = listener; + } + + void show() { + if (DEBUG) Log.d(TAG, "show()"); + + mFadeInAnimation.start(); + setAlpha(1.0f); + grantWindowFocus(true); + } + + void hide() { + if (DEBUG) Log.d(TAG, "hide()"); + + mFadeOutAnimation.start(); + setAlpha(0.0f); + grantWindowFocus(false); + } + + boolean isVisible() { + return getAlpha() == 1.0f; + } + + private void grantWindowFocus(boolean grantFocus) { + if (DEBUG) Log.d(TAG, "grantWindowFocus(" + grantFocus + ")"); + + try { + WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */, + getViewRootImpl().getInputToken(), grantFocus); + } catch (Exception e) { + Log.e(TAG, "Unable to update focus", e); + } + } + + void setAdditionalActions(List<RemoteAction> actions) { + if (DEBUG) Log.d(TAG, "setAdditionalActions()"); + + // Make sure we exactly as many additional buttons as we have actions to display. + final int actionsNumber = actions.size(); + int buttonsNumber = mAdditionalButtons.size(); + if (actionsNumber > buttonsNumber) { + final LayoutInflater layoutInflater = LayoutInflater.from(mContext); + // Add buttons until we have enough to display all of the actions. + while (actionsNumber > buttonsNumber) { + final TvPipMenuActionButton button = (TvPipMenuActionButton) layoutInflater.inflate( + R.layout.tv_pip_menu_additional_action_button, mActionButtonsContainer, + false); + button.setOnClickListener(this); + + mActionButtonsContainer.addView(button); + mAdditionalButtons.add(button); + + buttonsNumber++; + } + } else if (actionsNumber < buttonsNumber) { + // Hide buttons until we as many as the actions. + while (actionsNumber < buttonsNumber) { + final View button = mAdditionalButtons.get(buttonsNumber - 1); + button.setVisibility(View.GONE); + button.setTag(null); + + buttonsNumber--; + } + } + + // "Assign" actions to the buttons. + for (int index = 0; index < actionsNumber; index++) { + final RemoteAction action = actions.get(index); + final TvPipMenuActionButton button = mAdditionalButtons.get(index); + button.setVisibility(View.VISIBLE); // Ensure the button is visible. + button.setTextAndDescription(action.getContentDescription()); + button.setEnabled(action.isEnabled()); + button.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); + button.setTag(action); + + action.getIcon().loadDrawableAsync(mContext, drawable -> { + drawable.setTint(Color.WHITE); + button.setImageDrawable(drawable); + }, mUiThreadHandler); + } + } + + @Nullable + SurfaceControl getWindowSurfaceControl() { + final ViewRootImpl root = getViewRootImpl(); + if (root == null) { + return null; + } + final SurfaceControl out = root.getSurfaceControl(); + if (out != null && out.isValid()) { + return out; + } + return null; + } + + @Override + public void onClick(View v) { + if (mListener == null) return; + + final int id = v.getId(); + if (id == R.id.tv_pip_menu_fullscreen_button) { + mListener.onFullscreenButtonClick(); + } else if (id == R.id.tv_pip_menu_close_button) { + mListener.onCloseButtonClick(); + } else { + // This should be an "additional action" + final RemoteAction action = (RemoteAction) v.getTag(); + if (action != null) { + try { + action.getActionIntent().send(); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Failed to send action", e); + } + } else { + Log.w(TAG, "RemoteAction is null"); + } + } + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK + && mListener != null) { + mListener.onBackPress(); + return true; + } + return super.dispatchKeyEvent(event); + } + + interface Listener { + void onBackPress(); + void onCloseButtonClick(); + void onFullscreenButtonClick(); + } +} 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 index 5716c7fc7162..ce4b60893367 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 @@ package com.android.wm.shell.pip.tv; 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 @@ import java.util.Objects; * <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 @@ public class PipNotification { 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 @@ public class PipNotification { * 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 @@ public class PipNotification { 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 @@ public class PipNotification { 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/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 786a1fd896eb..8e24e0b516cb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -222,7 +222,11 @@ public class StartingSurfaceDrawer { } Context context = mContext; - final int theme = activityInfo.getThemeResource(); + int theme = activityInfo.getThemeResource(); + if (theme == 0) { + // replace with the default theme if the application didn't set + theme = com.android.internal.R.style.Theme_DeviceDefault_DayNight; + } if (DEBUG_SPLASH_SCREEN) { Slog.d(TAG, "addSplashScreen " + activityInfo.packageName + ": nonLocalizedLabel=" + nonLocalizedLabel + " theme=" 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 index 54863d23643a..7ce71b0a1158 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,8 +14,9 @@ * 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; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -25,20 +26,23 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemProperties; import android.util.ArrayMap; -import android.util.Slog; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.ITransitionPlayer; import android.window.TransitionInfo; +import android.window.WindowContainerTransaction; import android.window.WindowOrganizer; 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; @@ -59,8 +63,16 @@ public class Transitions { private final ShellExecutor mAnimExecutor; private final TransitionPlayerImpl mPlayerImpl; + /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */ + private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>(); + + private static final class ActiveTransition { + ArrayList<Animator> mAnimations = null; + TransitionHandler mFirstHandler = null; + } + /** Keeps track of currently tracked transitions and all the animations associated with each */ - private final ArrayMap<IBinder, ArrayList<Animator>> mActiveTransitions = new ArrayMap<>(); + private final ArrayMap<IBinder, ActiveTransition> mActiveTransitions = new ArrayMap<>(); public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { @@ -71,10 +83,27 @@ public class Transitions { mPlayerImpl = new TransitionPlayerImpl(); } + /** Register this transition handler with Core */ public void register(ShellTaskOrganizer taskOrganizer) { taskOrganizer.registerTransitionPlayer(mPlayerImpl); } + /** + * Adds a handler candidate. + * @see TransitionHandler + */ + public void addHandler(@NonNull TransitionHandler handler) { + mHandlers.add(handler); + } + + public ShellExecutor getMainExecutor() { + return mMainExecutor; + } + + public ShellExecutor getAnimExecutor() { + return mAnimExecutor; + } + // TODO(shell-transitions): real animations private void startExampleAnimation(@NonNull IBinder transition, @NonNull SurfaceControl leash, boolean show) { @@ -93,7 +122,7 @@ public class Transitions { transaction.apply(); mTransactionPool.release(transaction); mMainExecutor.execute(() -> { - mActiveTransitions.get(transition).remove(va); + mActiveTransitions.get(transition).mAnimations.remove(va); onFinish(transition); }); }; @@ -114,30 +143,23 @@ public class Transitions { @Override public void onAnimationRepeat(Animator animation) { } }); - mActiveTransitions.get(transition).add(va); + mActiveTransitions.get(transition).mAnimations.add(va); mAnimExecutor.execute(va::start); } - private static boolean isOpeningType(@WindowManager.TransitionType int type) { + /** @return true if the transition was triggered by opening something vs closing something */ + public static boolean isOpeningType(@WindowManager.TransitionType int type) { return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT || type == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; } - private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, + /** + * Reparents all participants into a shared parent and orders them based on: the global transit + * type, their transit mode, and their destination z-order. + */ + private static void setupStartState(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", - transitionToken, info); - // start task - if (!mActiveTransitions.containsKey(transitionToken)) { - Slog.e(TAG, "Got transitionReady for non-active transition " + transitionToken - + " expecting one of " + mActiveTransitions.keySet()); - } - if (mActiveTransitions.get(transitionToken) != null) { - throw new IllegalStateException("Got a duplicate onTransitionReady call for " - + transitionToken); - } - mActiveTransitions.put(transitionToken, new ArrayList<>()); boolean isOpening = isOpeningType(info.getType()); if (info.getRootLeash().isValid()) { t.show(info.getRootLeash()); @@ -148,24 +170,26 @@ public class Transitions { final SurfaceControl leash = change.getLeash(); final int mode = info.getChanges().get(i).getMode(); - // Don't animate anything with an animating parent + // Don't move anything with an animating parent if (change.getParent() != null) { - if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { + if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) { t.show(leash); t.setMatrix(leash, 1, 0, 0, 1); + t.setAlpha(leash, 1.f); + t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y); } continue; } t.reparent(leash, info.getRootLeash()); - t.setPosition(leash, change.getEndAbsBounds().left - info.getRootOffset().x, - change.getEndAbsBounds().top - info.getRootOffset().y); + t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, + change.getStartAbsBounds().top - info.getRootOffset().y); // Put all the OPEN/SHOW on top if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { t.show(leash); t.setMatrix(leash, 1, 0, 0, 1); if (isOpening) { - // put on top and fade in + // put on top with 0 alpha t.setLayer(leash, info.getChanges().size() - i); if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { // This received a transferred starting window, so make it immediately @@ -173,47 +197,155 @@ public class Transitions { t.setAlpha(leash, 1.f); } else { t.setAlpha(leash, 0.f); - startExampleAnimation(transitionToken, leash, true /* show */); } } else { - // put on bottom and leave it visible without fade + // put on bottom and leave it visible t.setLayer(leash, -i); t.setAlpha(leash, 1.f); } } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { if (isOpening) { - // put on bottom and leave visible without fade + // put on bottom and leave visible t.setLayer(leash, -i); } else { - // put on top and fade out + // put on top t.setLayer(leash, info.getChanges().size() - i); - startExampleAnimation(transitionToken, leash, false /* show */); } - } else { + } else { // CHANGE t.setLayer(leash, info.getChanges().size() - i); } } + } + + private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", + transitionToken, info); + final ActiveTransition active = mActiveTransitions.get(transitionToken); + if (active == null) { + throw new IllegalStateException("Got transitionReady for non-active transition " + + transitionToken + ". expecting one of " + mActiveTransitions.keySet()); + } + if (active.mAnimations != null) { + throw new IllegalStateException("Got a duplicate onTransitionReady call for " + + transitionToken); + } + if (!info.getRootLeash().isValid()) { + // Invalid root-leash implies that the transition is empty/no-op, so just do + // housekeeping and return. + t.apply(); + onFinish(transitionToken); + return; + } + + setupStartState(info, t); + + final Runnable finishRunnable = () -> onFinish(transitionToken); + // If a handler chose to uniquely run this animation, try delegating to it. + if (active.mFirstHandler != null && active.mFirstHandler.startAnimation( + transitionToken, info, t, finishRunnable)) { + return; + } + // Otherwise give every other handler a chance (in order) + for (int i = mHandlers.size() - 1; i >= 0; --i) { + if (mHandlers.get(i) == active.mFirstHandler) continue; + if (mHandlers.get(i).startAnimation(transitionToken, info, t, finishRunnable)) { + return; + } + } + + // No handler chose to perform this animation, so fall-back to the + // default animation handling. + final boolean isOpening = isOpeningType(info.getType()); + active.mAnimations = new ArrayList<>(); // Play fade animations + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + + // Don't animate anything with an animating parent + if (change.getParent() != null) continue; + + final int mode = info.getChanges().get(i).getMode(); + if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { + if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { + // This received a transferred starting window, so don't animate + continue; + } + // fade in + startExampleAnimation(transitionToken, change.getLeash(), true /* show */); + } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { + // fade out + startExampleAnimation(transitionToken, change.getLeash(), false /* show */); + } + } t.apply(); onFinish(transitionToken); } private void onFinish(IBinder transition) { - if (!mActiveTransitions.get(transition).isEmpty()) return; + final ActiveTransition active = mActiveTransitions.get(transition); + if (active.mAnimations != null && !active.mAnimations.isEmpty()) return; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animations finished, notifying core %s", transition); mActiveTransitions.remove(transition); mOrganizer.finishTransition(transition, null, null); } - private void requestStartTransition(int type, @NonNull IBinder transitionToken) { + private void requestStartTransition(int type, @NonNull IBinder transitionToken, + @Nullable ActivityManager.RunningTaskInfo triggerTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s", type, transitionToken); if (mActiveTransitions.containsKey(transitionToken)) { throw new RuntimeException("Transition already started " + transitionToken); } - IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */); - mActiveTransitions.put(transition, null); + final ActiveTransition active = new ActiveTransition(); + WindowContainerTransaction wct = null; + for (int i = mHandlers.size() - 1; i >= 0; --i) { + wct = mHandlers.get(i).handleRequest(type, transitionToken, triggerTask); + if (wct != null) { + active.mFirstHandler = mHandlers.get(i); + break; + } + } + IBinder transition = mOrganizer.startTransition(type, transitionToken, wct); + mActiveTransitions.put(transition, active); + } + + /** Start a new transition directly. */ + public IBinder startTransition(@WindowManager.TransitionType int type, + @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) { + final ActiveTransition active = new ActiveTransition(); + active.mFirstHandler = handler; + IBinder transition = mOrganizer.startTransition(type, null /* token */, wct); + mActiveTransitions.put(transition, active); + return transition; + } + + /** + * Interface for something which can handle a subset of transitions. + */ + public interface TransitionHandler { + /** + * Starts a transition animation. This is always called if handleRequest returned non-null + * for a particular transition. Otherwise, it is only called if no other handler before + * it handled the transition. + * + * @return true if transition was handled, false if not (falls-back to default). + */ + boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback); + + /** + * Potentially handles a startTransition request. + * @param type The transition type + * @param triggerTask The task which triggered this transition request. + * @return WCT to apply with transition-start or null if this handler isn't handling + * the request. + */ + @Nullable + WindowContainerTransaction handleRequest(@WindowManager.TransitionType int type, + @NonNull IBinder transition, + @Nullable ActivityManager.RunningTaskInfo triggerTask); } @BinderThread @@ -227,9 +359,10 @@ public class Transitions { } @Override - public void requestStartTransition(int i, IBinder iBinder) throws RemoteException { + public void requestStartTransition(int i, IBinder iBinder, + ActivityManager.RunningTaskInfo runningTaskInfo) throws RemoteException { mMainExecutor.execute(() -> { - Transitions.this.requestStartTransition(i, iBinder); + Transitions.this.requestStartTransition(i, iBinder, runningTaskInfo); }); } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt index 5125a3972cf4..796c8c4a6ad0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt @@ -31,6 +31,10 @@ const val TEST_APP_PIP_MENU_ACTION_CLEAR = "Clear" // Test App > Ime Activity const val TEST_APP_IME_ACTIVITY_LABEL = "ImeApp" +const val TEST_APP_IME_ACTIVITY_ACTION_OPEN_IME = + "com.android.wm.shell.flicker.testapp.action.OPEN_IME" +const val TEST_APP_IME_ACTIVITY_ACTION_CLOSE_IME = + "com.android.wm.shell.flicker.testapp.action.CLOSE_IME" // Test App > Test Activity const val TEST_APP_FIXED_ACTIVITY_LABEL = "FixedApp" diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt index 6fd1df3b3f30..f8efd0e9c761 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt @@ -62,8 +62,9 @@ abstract class BaseAppHelper( val ui: UiObject2? get() = uiDevice.findObject(appSelector) - fun launchViaIntent(stringExtras: Map<String, String> = mapOf()) { + fun launchViaIntent(action: String? = null, stringExtras: Map<String, String> = mapOf()) { val intent = openAppIntent + intent.action = action stringExtras.forEach() { intent.putExtra(it.key, it.value) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt index c546a4d18027..eb20d73cdcd1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt @@ -20,6 +20,8 @@ import android.app.Instrumentation import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.FIND_TIMEOUT +import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_ACTION_CLOSE_IME +import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_ACTION_OPEN_IME import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_LABEL import com.android.wm.shell.flicker.testapp.Components import org.junit.Assert @@ -32,15 +34,27 @@ open class ImeAppHelper( Components.ImeActivity() ) { fun openIME() { - val editText = uiDevice.wait( - Until.findObject(By.res(getPackage(), "plain_text_input")), - FIND_TIMEOUT) - Assert.assertNotNull("Text field not found, this usually happens when the device " + - "was left in an unknown state (e.g. in split screen)", editText) - editText.click() + if (!isTelevision) { + val editText = uiDevice.wait( + Until.findObject(By.res(getPackage(), "plain_text_input")), + FIND_TIMEOUT) + Assert.assertNotNull("Text field not found, this usually happens when the device " + + "was left in an unknown state (e.g. in split screen)", editText) + editText.click() + } else { + // If we do the same thing as above - editText.click() - on TV, that's going to force TV + // into the touch mode. We really don't want that. + launchViaIntent(action = TEST_APP_IME_ACTIVITY_ACTION_OPEN_IME) + } } fun closeIME() { - uiDevice.pressBack() + if (!isTelevision) { + uiDevice.pressBack() + } else { + // While pressing the back button should close the IME on TV as well, it may also lead + // to the app closing. So let's instead just ask the app to close the IME. + launchViaIntent(action = TEST_APP_IME_ACTIVITY_ACTION_CLOSE_IME) + } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt index 66efb5ae3c2d..6105f50562d7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt @@ -208,8 +208,8 @@ class TvPipMenuTests : TvPipTestBase() { @Test fun pipMenu_customActions_override_mediaControls() { // Start media session before entering PiP with custom actions. - testApp.clickStartMediaSessionButton() testApp.checkWithCustomActionsCheckbox() + testApp.clickStartMediaSessionButton() enterPip_openMenu_assertShown() // PiP menu should contain "No-Op", "Off" and "Clear" buttons for the custom actions... 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 75388bf2a189..5258e9030075 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 @@ private val StatusBarNotification.deleteIntent: PendingIntent? 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/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt index 587b5510b0b4..1b73920046dc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt @@ -26,78 +26,109 @@ import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME /** Id of the root view in the com.android.wm.shell.pip.tv.PipMenuActivity */ private const val TV_PIP_MENU_ROOT_ID = "tv_pip_menu" -private const val TV_PIP_MENU_CONTROLS_ID = "pip_controls" -private const val TV_PIP_MENU_CLOSE_BUTTON_ID = "close_button" -private const val TV_PIP_MENU_FULLSCREEN_BUTTON_ID = "full_button" +private const val TV_PIP_MENU_BUTTONS_CONTAINER_ID = "tv_pip_menu_action_buttons" +private const val TV_PIP_MENU_CLOSE_BUTTON_ID = "tv_pip_menu_close_button" +private const val TV_PIP_MENU_FULLSCREEN_BUTTON_ID = "tv_pip_menu_fullscreen_button" private const val FOCUS_ATTEMPTS = 10 private const val WAIT_TIME_MS = 3_000L +private val TV_PIP_MENU_SELECTOR = + By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_ROOT_ID) +private val TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR = + By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_BUTTONS_CONTAINER_ID) private val TV_PIP_MENU_CLOSE_BUTTON_SELECTOR = By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_CLOSE_BUTTON_ID) private val TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR = By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_FULLSCREEN_BUTTON_ID) -private val tvPipMenuSelector = By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_ROOT_ID) - -fun UiDevice.pressWindowKey() = pressKeyCode(KeyEvent.KEYCODE_WINDOW) - fun UiDevice.waitForTvPipMenu(): UiObject2? = - wait(Until.findObject(tvPipMenuSelector), WAIT_TIME_MS) + wait(Until.findObject(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS) -fun UiDevice.waitForTvPipMenuToClose(): Boolean = wait(Until.gone(tvPipMenuSelector), WAIT_TIME_MS) +fun UiDevice.waitForTvPipMenuToClose(): Boolean = + wait(Until.gone(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS) fun UiDevice.findTvPipMenuControls(): UiObject2? = - findObject(tvPipMenuSelector) - ?.findObject(By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_CONTROLS_ID)) + findTvPipMenuElement(TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR) fun UiDevice.findTvPipMenuCloseButton(): UiObject2? = - findObject(tvPipMenuSelector)?.findObject(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR) + findTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR) + +fun UiDevice.findTvPipMenuFullscreenButton(): UiObject2? = + findTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR) + +fun UiDevice.findTvPipMenuElementWithDescription(desc: String): UiObject2? = + findTvPipMenuElement(By.desc(desc)) + +private fun UiDevice.findTvPipMenuElement(selector: BySelector): UiObject2? = + findObject(TV_PIP_MENU_SELECTOR)?.findObject(selector) + +fun UiDevice.waitForTvPipMenuElementWithDescription(desc: String): UiObject2? { + // Ideally, we'd want to wait for an element with the given description that has the Pip Menu as + // its parent, but the API does not allow us to construct a query exactly that way. + // So instead we'll wait for a Pip Menu that has the element, which we are looking for, as a + // descendant and then retrieve the element from the menu and return to the caller of this + // method. + val elementSelector = By.desc(desc) + val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR).hasDescendant(elementSelector) + + return wait(Until.findObject(menuContainingElementSelector), WAIT_TIME_MS) + ?.findObject(elementSelector) +} + +fun UiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(desc: String): Boolean? { + val elementSelector = By.desc(desc) + val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR).hasDescendant(elementSelector) + + return wait(Until.gone(menuContainingElementSelector), WAIT_TIME_MS) +} fun UiDevice.clickTvPipMenuCloseButton() { - focusOnObjectInTvPipMenu(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR) || + focusOnAndClickTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR) || error("Could not focus on the Close button") - pressDPadCenter() } -fun UiDevice.findTvPipMenuFullscreenButton(): UiObject2? = - findObject(tvPipMenuSelector)?.findObject(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR) - fun UiDevice.clickTvPipMenuFullscreenButton() { - focusOnObjectInTvPipMenu(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR) || + focusOnAndClickTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR) || error("Could not focus on the Fullscreen button") - pressDPadCenter() } -fun UiDevice.findTvPipMenuElementWithDescription(desc: String): UiObject2? = - findObject(tvPipMenuSelector)?.findObject(By.desc(desc).pkg(SYSTEM_UI_PACKAGE_NAME)) - fun UiDevice.clickTvPipMenuElementWithDescription(desc: String) { - focusOnObjectInTvPipMenu(By.desc(desc).pkg(SYSTEM_UI_PACKAGE_NAME)) || + focusOnAndClickTvPipMenuElement(By.desc(desc).pkg(SYSTEM_UI_PACKAGE_NAME)) || error("Could not focus on the Pip menu object with \"$desc\" description") - pressDPadCenter() + // So apparently Accessibility framework on TV is not very reliable and sometimes the state of + // the tree of accessibility nodes as seen by the accessibility clients kind of lags behind of + // the "real" state of the "UI tree". It seems, however, that moving focus around the tree + // forces the AccessibilityNodeInfo tree to get properly updated. + // So since we suspect that clicking on a Pip Menu element may cause some UI changes and we want + // those changes to be seen by the UiAutomator, which is using Accessibility framework under the + // hood for inspecting UI, we'll move the focus around a little. + moveFocus() } -fun UiDevice.waitForTvPipMenuElementWithDescription(desc: String): UiObject2? { - val buttonSelector = By.desc(desc) - val menuWithButtonSelector = By.copy(tvPipMenuSelector).hasDescendant(buttonSelector) - return wait(Until.findObject(menuWithButtonSelector), WAIT_TIME_MS) - ?.findObject(buttonSelector) -} - -fun UiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(desc: String): Boolean? = - wait(Until.gone(By.copy(tvPipMenuSelector).hasDescendant(By.desc(desc))), WAIT_TIME_MS) +private fun UiDevice.focusOnAndClickTvPipMenuElement(selector: BySelector): Boolean { + repeat(FOCUS_ATTEMPTS) { + val element = findTvPipMenuElement(selector) + ?: error("The Pip Menu element we try to focus on is gone.") + + if (element.isFocusedOrHasFocusedChild) { + pressDPadCenter() + return true + } + + findTvPipMenuElement(By.focused(true))?.let { focused -> + if (element.visibleCenter.x < focused.visibleCenter.x) + pressDPadLeft() else pressDPadRight() + waitForIdle() + } ?: error("Pip menu does not contain a focused element") + } -fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean = visibleBounds.run { - height() == uiDevice.displayHeight && width() == uiDevice.displayWidth + return false } -val UiObject2.isFocusedOrHasFocusedChild: Boolean - get() = isFocused || findObject(By.focused(true)) != null - fun UiDevice.closeTvPipWindow() { // Check if Pip menu is Open. If it's not, open it. - if (findObject(tvPipMenuSelector) == null) { + if (findObject(TV_PIP_MENU_SELECTOR) == null) { pressWindowKey() waitForTvPipMenu() ?: error("Could not open Pip menu") } @@ -106,17 +137,25 @@ fun UiDevice.closeTvPipWindow() { waitForTvPipMenuToClose() } -private fun UiDevice.focusOnObjectInTvPipMenu(objectSelector: BySelector): Boolean { - repeat(FOCUS_ATTEMPTS) { - val menu = findObject(tvPipMenuSelector) ?: error("Pip Menu is now shown") - val objectToFocus = menu.findObject(objectSelector) - .apply { if (isFocusedOrHasFocusedChild) return true } - ?: error("The object we try to focus on is gone.") - val currentlyFocused = menu.findObject(By.focused(true)) - ?: error("Pip menu does not contain a focused element") - if (objectToFocus.visibleCenter.x < currentlyFocused.visibleCenter.x) - pressDPadLeft() else pressDPadRight() - waitForIdle() - } - return false -}
\ No newline at end of file +/** + * Simply presses the D-Pad Left and Right buttons once, which should move the focus on the screen, + * which should cause Accessibility events to be fired, which should, hopefully, properly update + * AccessibilityNodeInfo tree dispatched by the platform to the Accessibility services, one of which + * is the UiAutomator. + */ +private fun UiDevice.moveFocus() { + waitForIdle() + pressDPadLeft() + waitForIdle() + pressDPadRight() + waitForIdle() +} + +fun UiDevice.pressWindowKey() = pressKeyCode(KeyEvent.KEYCODE_WINDOW) + +fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean = visibleBounds.run { + height() == uiDevice.displayHeight && width() == uiDevice.displayWidth +} + +val UiObject2.isFocusedOrHasFocusedChild: Boolean + get() = isFocused || findObject(By.focused(true)) != null diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml index 28ed3431db62..5549330df766 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml @@ -53,6 +53,7 @@ <activity android:name=".ImeActivity" android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity" android:label="ImeApp" + android:launchMode="singleTop" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java index 856728715c1c..59c64a1345ab 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java @@ -17,10 +17,21 @@ package com.android.wm.shell.flicker.testapp; import android.app.Activity; +import android.content.Intent; import android.os.Bundle; +import android.view.View; import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; public class ImeActivity extends Activity { + private static final String ACTION_OPEN_IME = + "com.android.wm.shell.flicker.testapp.action.OPEN_IME"; + private static final String ACTION_CLOSE_IME = + "com.android.wm.shell.flicker.testapp.action.CLOSE_IME"; + + private InputMethodManager mImm; + private View mEditText; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -29,5 +40,27 @@ public class ImeActivity extends Activity { .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; getWindow().setAttributes(p); setContentView(R.layout.activity_ime); + + mEditText = findViewById(R.id.plain_text_input); + mImm = getSystemService(InputMethodManager.class); + + handleIntent(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleIntent(intent); + } + + private void handleIntent(Intent intent) { + final String action = intent.getAction(); + if (ACTION_OPEN_IME.equals(action)) { + mEditText.requestFocus(); + mImm.showSoftInput(mEditText, InputMethodManager.SHOW_FORCED); + } else if (ACTION_CLOSE_IME.equals(action)) { + mImm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); + mEditText.clearFocus(); + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java index 01b520486011..eb03ab0a9802 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java @@ -99,7 +99,7 @@ public class TaskViewTest extends ShellTestCase { when(mOrganizer.getExecutor()).thenReturn(mExecutor); mTaskView = new TaskView(mContext, mOrganizer); - mTaskView.setListener(mViewListener); + mTaskView.setListener(mExecutor, mViewListener); } @After @@ -112,9 +112,9 @@ public class TaskViewTest extends ShellTestCase { @Test public void testSetPendingListener_throwsException() { TaskView taskView = new TaskView(mContext, mOrganizer); - taskView.setListener(mViewListener); + taskView.setListener(mExecutor, mViewListener); try { - taskView.setListener(mViewListener); + taskView.setListener(mExecutor, mViewListener); } catch (IllegalStateException e) { // pass return; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java index 069305212958..bde04b604c79 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java @@ -71,7 +71,7 @@ public class BubbleTest extends ShellTestCase { Intent target = new Intent(mContext, BubblesTestActivity.class); Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder( - PendingIntent.getActivity(mContext, 0, target, 0), + PendingIntent.getActivity(mContext, 0, target, PendingIntent.FLAG_MUTABLE), Icon.createWithResource(mContext, R.drawable.bubble_ic_create_bubble)) .build(); when(mSbn.getNotification()).thenReturn(mNotif); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java index 595440f8d52d..f10dc16fae5c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java @@ -27,6 +27,8 @@ import android.testing.TestableLooper; import androidx.test.InstrumentationRegistry; 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; @@ -44,12 +46,14 @@ public class HideDisplayCutoutControllerTest { private HideDisplayCutoutController mHideDisplayCutoutController; @Mock private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer; + @Mock + private ShellExecutor mMockMainExecutor; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mHideDisplayCutoutController = new HideDisplayCutoutController( - mContext, mMockDisplayAreaOrganizer); + mContext, mMockDisplayAreaOrganizer, mMockMainExecutor); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java index e0c835b6dc6c..963757045453 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java @@ -50,6 +50,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; import org.junit.Before; import org.junit.Test; @@ -72,6 +73,9 @@ public class HideDisplayCutoutOrganizerTest { private DisplayController mMockDisplayController; private HideDisplayCutoutOrganizer mOrganizer; + @Mock + private ShellExecutor mMockMainExecutor; + private DisplayAreaInfo mDisplayAreaInfo; private SurfaceControl mLeash; @@ -93,7 +97,7 @@ public class HideDisplayCutoutOrganizerTest { when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay); HideDisplayCutoutOrganizer organizer = new HideDisplayCutoutOrganizer( - mContext, mMockDisplayController, Runnable::run); + mContext, mMockDisplayController, mMockMainExecutor); mOrganizer = Mockito.spy(organizer); doNothing().when(mOrganizer).unregisterOrganizer(); doNothing().when(mOrganizer).applyBoundsAndOffsets(any(), any(), any(), any()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java new file mode 100644 index 000000000000..e9c4af12a0d6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java @@ -0,0 +1,110 @@ +/* + * 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 static android.view.Display.DEFAULT_DISPLAY; +import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.Display; +import android.view.SurfaceControl; +import android.window.DisplayAreaInfo; +import android.window.IWindowContainerToken; +import android.window.WindowContainerToken; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.common.DisplayController; + +import org.junit.Before; +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 OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase { + private DisplayAreaInfo mDisplayAreaInfo; + private Display mDisplay; + private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer; + private WindowContainerToken mToken; + private SurfaceControl mLeash; + private TestableLooper mTestableLooper; + + @Mock + IWindowContainerToken mMockRealToken; + @Mock + DisplayController mMockDisplayController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTestableLooper = TestableLooper.get(this); + mToken = new WindowContainerToken(mMockRealToken); + mLeash = new SurfaceControl(); + mDisplay = mContext.getDisplay(); + when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay); + mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY, + FEATURE_ONE_HANDED_BACKGROUND_PANEL); + + mBackgroundPanelOrganizer = new OneHandedBackgroundPanelOrganizer(mContext, + mMockDisplayController, Runnable::run); + } + + @Test + public void testOnDisplayAreaAppeared() { + mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); + mTestableLooper.processAllMessages(); + + assertThat(mBackgroundPanelOrganizer.getBackgroundSurface()).isNotNull(); + } + + @Test + public void testUnregisterOrganizer() { + mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); + mTestableLooper.processAllMessages(); + mBackgroundPanelOrganizer.unregisterOrganizer(); + + assertThat(mBackgroundPanelOrganizer.getBackgroundSurface()).isNull(); + } + + @Test + public void testShowBackgroundLayer() { + mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); + mBackgroundPanelOrganizer.showBackgroundPanelLayer(); + mTestableLooper.processAllMessages(); + + assertThat(mBackgroundPanelOrganizer.mIsShowing).isTrue(); + } + + @Test + public void testRemoveBackgroundLayer() { + mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); + mBackgroundPanelOrganizer.removeBackgroundPanelLayer(); + mTestableLooper.processAllMessages(); + + assertThat(mBackgroundPanelOrganizer.mIsShowing).isFalse(); + } +} 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 e37e1548775a..20184bfd5541 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 @@ -54,6 +54,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Mock DisplayController mMockDisplayController; @Mock + OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer; + @Mock OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer; @Mock OneHandedTouchHandler mMockTouchHandler; @@ -75,6 +77,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { OneHandedController oneHandedController = new OneHandedController( mContext, mMockDisplayController, + mMockBackgroundOrganizer, mMockDisplayAreaOrganizer, mMockTouchHandler, mMockTutorialHandler, @@ -94,7 +97,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { mContext); OneHandedDisplayAreaOrganizer displayAreaOrganizer = new OneHandedDisplayAreaOrganizer( mContext, mMockDisplayController, animationController, mMockTutorialHandler, - Runnable::run); + Runnable::run, mMockBackgroundOrganizer); 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 5d742b3531b9..3d9fad9097f8 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 @@ -80,6 +80,8 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { SurfaceControl mMockLeash; @Mock WindowContainerTransaction mMockWindowContainerTransaction; + @Mock + OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer; Handler mSpyUpdateHandler; Handler.Callback mUpdateCallback = (msg) -> false; @@ -103,7 +105,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mMockSurfaceTransactionHelper); when(mMockAnimator.isRunning()).thenReturn(true); when(mMockAnimator.setDuration(anyInt())).thenReturn(mFakeAnimator); - when(mMockAnimator.setOneHandedAnimationCallbacks(any())).thenReturn(mFakeAnimator); + when(mMockAnimator.addOneHandedAnimationCallback(any())).thenReturn(mFakeAnimator); when(mMockAnimator.setTransitionDirection(anyInt())).thenReturn(mFakeAnimator); when(mMockLeash.getWidth()).thenReturn(DISPLAY_WIDTH); when(mMockLeash.getHeight()).thenReturn(DISPLAY_HEIGHT); @@ -112,7 +114,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mMockDisplayController, mMockAnimationController, mTutorialHandler, - Runnable::run); + Runnable::run, mMockBackgroundOrganizer); mSpyUpdateHandler = spy(new Handler(OneHandedThread.get().getLooper(), mUpdateCallback)); mDisplayAreaOrganizer.setUpdateHandler(mSpyUpdateHandler); } 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 ba8c737924f4..b187dc981bf8 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 @@ -45,6 +45,8 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { @Mock DisplayController mMockDisplayController; @Mock + OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer; + @Mock OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer; @Mock IOverlayManager mMockOverlayManager; @@ -59,6 +61,7 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { mOneHandedController = new OneHandedController( getContext(), mMockDisplayController, + mMockBackgroundOrganizer, mMockDisplayAreaOrganizer, mTouchHandler, mTutorialHandler, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 4efaebf96c2b..b4cfbc281d61 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -73,7 +73,7 @@ public class PipTouchHandlerTest extends ShellTestCase { private PipUiEventLogger mPipUiEventLogger; @Mock - private ShellExecutor mShellMainExecutor; + private ShellExecutor mMainExecutor; private PipBoundsState mPipBoundsState; private PipBoundsAlgorithm mPipBoundsAlgorithm; @@ -98,7 +98,7 @@ public class PipTouchHandlerTest extends ShellTestCase { mPipSnapAlgorithm = new PipSnapAlgorithm(); mPipTouchHandler = new PipTouchHandler(mContext, mPhonePipMenuController, mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer, - mFloatingContentCoordinator, mPipUiEventLogger, mShellMainExecutor); + mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor); mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper()); mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler()); mPipTouchHandler.setPipMotionHelper(mMotionHelper); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index 07115c2b4d22..c9537afa37ef 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -15,14 +15,13 @@ */ package unittest.src.com.android.wm.shell.startingsurface; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; - import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -72,6 +71,7 @@ public class StartingSurfaceDrawerTests { static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{ int mAddWindowForTask = 0; + int mViewThemeResId; TestStartingSurfaceDrawer(Context context, ShellExecutor executor) { super(context, executor); @@ -82,6 +82,7 @@ public class StartingSurfaceDrawerTests { View view, WindowManager wm, WindowManager.LayoutParams params) { // listen for addView mAddWindowForTask = taskId; + mViewThemeResId = view.getContext().getThemeResId(); } @Override @@ -121,7 +122,7 @@ public class StartingSurfaceDrawerTests { final int taskId = 1; final Handler mainLoop = new Handler(Looper.getMainLooper()); final StartingWindowInfo windowInfo = - createWindowInfo(taskId, WINDOWING_MODE_FULLSCREEN); + createWindowInfo(taskId, android.R.style.Theme); mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder); waitHandlerIdle(mainLoop); verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any()); @@ -133,12 +134,24 @@ public class StartingSurfaceDrawerTests { assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0); } - private StartingWindowInfo createWindowInfo(int taskId, int windowingMode) { + @Test + public void testFallbackDefaultTheme() { + final int taskId = 1; + final Handler mainLoop = new Handler(Looper.getMainLooper()); + final StartingWindowInfo windowInfo = + createWindowInfo(taskId, 0); + mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder); + waitHandlerIdle(mainLoop); + verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any()); + assertNotEquals(mStartingSurfaceDrawer.mViewThemeResId, 0); + } + + private StartingWindowInfo createWindowInfo(int taskId, int themeResId) { StartingWindowInfo windowInfo = new StartingWindowInfo(); final ActivityInfo info = new ActivityInfo(); info.applicationInfo = new ApplicationInfo(); info.packageName = "test"; - info.theme = android.R.style.Theme; + info.theme = themeResId; final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); taskInfo.topActivityInfo = info; taskInfo.taskId = taskId; diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 8330363f3c84..b54f7d81274c 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -100,6 +100,11 @@ cc_library { "libz", ], }, + linux_glibc: { + srcs: [ + "CursorWindow.cpp", + ], + }, windows: { enabled: true, }, diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index cb56a5172a45..011a0de8031f 100755 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -385,7 +385,7 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap return {}; } - auto overlay_path = loaded_idmap->OverlayApkPath(); + auto overlay_path = std::string(loaded_idmap->OverlayApkPath()); auto assets = ZipAssetsProvider::Create(overlay_path); return (assets) ? LoadImpl(std::move(assets), overlay_path, flags | PROPERTY_OVERLAY, nullptr /* override_asset */, std::move(idmap_asset), diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index bec80a7d605e..3f0600040139 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -157,7 +157,8 @@ void AssetManager2::BuildDynamicRefTable() { // The target package must precede the overlay package in the apk assets paths in order // to take effect. const auto& loaded_idmap = apk_assets->GetLoadedIdmap(); - auto target_package_iter = apk_assets_package_ids.find(loaded_idmap->TargetApkPath()); + auto target_package_iter = apk_assets_package_ids.find( + std::string(loaded_idmap->TargetApkPath())); if (target_package_iter == apk_assets_package_ids.end()) { LOG(INFO) << "failed to find target package for overlay " << loaded_idmap->OverlayApkPath(); diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index a61309514143..73e040c42826 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -36,13 +36,51 @@ using ::android::base::StringPrintf; namespace android { -uint32_t round_to_4_bytes(uint32_t size) { - return size + (4U - (size % 4U)) % 4U; -} +// See frameworks/base/cmds/idmap2/include/idmap2/Idmap.h for full idmap file format specification. +struct Idmap_header { + // Always 0x504D4449 ('IDMP') + uint32_t magic; + uint32_t version; -size_t Idmap_header::Size() const { - return sizeof(Idmap_header) + sizeof(uint8_t) * round_to_4_bytes(dtohl(debug_info_size)); -} + uint32_t target_crc32; + uint32_t overlay_crc32; + + uint32_t fulfilled_policies; + uint32_t enforce_overlayable; + + // overlay_path, target_path, and other string values encoded in the idmap header and read and + // stored in separate structures. This allows the idmap header data to be casted to this struct + // without having to read/store each header entry separately. +}; + +struct Idmap_data_header { + uint8_t target_package_id; + uint8_t overlay_package_id; + + // Padding to ensure 4 byte alignment for target_entry_count + uint16_t p0; + + uint32_t target_entry_count; + uint32_t target_inline_entry_count; + uint32_t overlay_entry_count; + + uint32_t string_pool_index_offset; +}; + +struct Idmap_target_entry { + uint32_t target_id; + uint32_t overlay_id; +}; + +struct Idmap_target_entry_inline { + uint32_t target_id; + Res_value value; +}; + +struct Idmap_overlay_entry { + uint32_t overlay_id; + uint32_t target_id; +}; OverlayStringPool::OverlayStringPool(const LoadedIdmap* loaded_idmap) : data_header_(loaded_idmap->data_header_), @@ -155,140 +193,133 @@ IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const { return {}; } -static bool is_word_aligned(const void* data) { - return (reinterpret_cast<uintptr_t>(data) & 0x03U) == 0U; -} - -static bool IsValidIdmapHeader(const StringPiece& data) { - if (!is_word_aligned(data.data())) { - LOG(ERROR) << "Idmap header is not word aligned."; - return false; +namespace { +template <typename T> +const T* ReadType(const uint8_t** in_out_data_ptr, size_t* in_out_size, const std::string& label, + size_t count = 1) { + if (!util::IsFourByteAligned(*in_out_data_ptr)) { + LOG(ERROR) << "Idmap " << label << " is not word aligned."; + return {}; } - - if (data.size() < sizeof(Idmap_header)) { - LOG(ERROR) << "Idmap header is too small."; - return false; + if ((*in_out_size / sizeof(T)) < count) { + LOG(ERROR) << "Idmap too small for the number of " << label << " entries (" + << count << ")."; + return nullptr; } + auto data_ptr = *in_out_data_ptr; + const size_t read_size = sizeof(T) * count; + *in_out_data_ptr += read_size; + *in_out_size -= read_size; + return reinterpret_cast<const T*>(data_ptr); +} - auto header = reinterpret_cast<const Idmap_header*>(data.data()); - if (dtohl(header->magic) != kIdmapMagic) { - LOG(ERROR) << StringPrintf("Invalid Idmap file: bad magic value (was 0x%08x, expected 0x%08x)", - dtohl(header->magic), kIdmapMagic); - return false; +std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size_t* in_out_size, + const std::string& label) { + const auto* len = ReadType<uint32_t>(in_out_data_ptr, in_out_size, label + " length"); + if (len == nullptr) { + return {}; } - - if (dtohl(header->version) != kIdmapCurrentVersion) { - // We are strict about versions because files with this format are auto-generated and don't need - // backwards compatibility. - LOG(ERROR) << StringPrintf("Version mismatch in Idmap (was 0x%08x, expected 0x%08x)", - dtohl(header->version), kIdmapCurrentVersion); - return false; + const auto* data = ReadType<char>(in_out_data_ptr, in_out_size, label, *len); + if (data == nullptr) { + return {}; } - - return true; + // Strings are padded to the next 4 byte boundary. + const uint32_t padding_size = (4U - ((size_t)*in_out_data_ptr & 0x3U)) % 4U; + for (uint32_t i = 0; i < padding_size; i++) { + if (**in_out_data_ptr != 0) { + LOG(ERROR) << " Idmap padding of " << label << " is non-zero."; + return {}; + } + *in_out_data_ptr += sizeof(uint8_t); + *in_out_size -= sizeof(uint8_t); + } + return std::string_view(data, *len); +} } LoadedIdmap::LoadedIdmap(std::string&& idmap_path, - const time_t last_mod_time, const Idmap_header* header, const Idmap_data_header* data_header, const Idmap_target_entry* target_entries, const Idmap_target_entry_inline* target_inline_entries, const Idmap_overlay_entry* overlay_entries, - ResStringPool* string_pool) + std::unique_ptr<ResStringPool>&& string_pool, + std::string_view overlay_apk_path, + std::string_view target_apk_path) : header_(header), data_header_(data_header), target_entries_(target_entries), target_inline_entries_(target_inline_entries), overlay_entries_(overlay_entries), - string_pool_(string_pool), + string_pool_(std::move(string_pool)), idmap_path_(std::move(idmap_path)), - idmap_last_mod_time_(last_mod_time) { - - size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path), - arraysize(header_->overlay_path)); - overlay_apk_path_.assign(reinterpret_cast<const char*>(header_->overlay_path), length); - - length = strnlen(reinterpret_cast<const char*>(header_->target_path), - arraysize(header_->target_path)); - target_apk_path_.assign(reinterpret_cast<const char*>(header_->target_path), length); -} + overlay_apk_path_(overlay_apk_path), + target_apk_path_(target_apk_path), + idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {} std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path, const StringPiece& idmap_data) { ATRACE_CALL(); - if (!IsValidIdmapHeader(idmap_data)) { + size_t data_size = idmap_data.size(); + auto data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data()); + + // Parse the idmap header + auto header = ReadType<Idmap_header>(&data_ptr, &data_size, "header"); + if (header == nullptr) { return {}; } - - auto header = reinterpret_cast<const Idmap_header*>(idmap_data.data()); - const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data()) + header->Size(); - size_t data_size = idmap_data.size() - header->Size(); - - // Currently idmap2 can only generate one data block. - auto data_header = reinterpret_cast<const Idmap_data_header*>(data_ptr); - data_ptr += sizeof(*data_header); - data_size -= sizeof(*data_header); - - // Make sure there is enough space for the target entries declared in the header - const auto target_entries = reinterpret_cast<const Idmap_target_entry*>(data_ptr); - if (data_size / sizeof(Idmap_target_entry) < - static_cast<size_t>(dtohl(data_header->target_entry_count))) { - LOG(ERROR) << StringPrintf("Idmap too small for the number of target entries (%d)", - (int)dtohl(data_header->target_entry_count)); + if (dtohl(header->magic) != kIdmapMagic) { + LOG(ERROR) << StringPrintf("Invalid Idmap file: bad magic value (was 0x%08x, expected 0x%08x)", + dtohl(header->magic), kIdmapMagic); return {}; } - - // Advance the data pointer past the target entries. - const size_t target_entry_size_bytes = - (dtohl(data_header->target_entry_count) * sizeof(Idmap_target_entry)); - data_ptr += target_entry_size_bytes; - data_size -= target_entry_size_bytes; - - // Make sure there is enough space for the target entries declared in the header. - const auto target_inline_entries = reinterpret_cast<const Idmap_target_entry_inline*>(data_ptr); - if (data_size / sizeof(Idmap_target_entry_inline) < - static_cast<size_t>(dtohl(data_header->target_inline_entry_count))) { - LOG(ERROR) << StringPrintf("Idmap too small for the number of target inline entries (%d)", - (int)dtohl(data_header->target_inline_entry_count)); + if (dtohl(header->version) != kIdmapCurrentVersion) { + // We are strict about versions because files with this format are generated at runtime and + // don't need backwards compatibility. + LOG(ERROR) << StringPrintf("Version mismatch in Idmap (was 0x%08x, expected 0x%08x)", + dtohl(header->version), kIdmapCurrentVersion); return {}; } - - // Advance the data pointer past the target entries. - const size_t target_inline_entry_size_bytes = - (dtohl(data_header->target_inline_entry_count) * sizeof(Idmap_target_entry_inline)); - data_ptr += target_inline_entry_size_bytes; - data_size -= target_inline_entry_size_bytes; - - // Make sure there is enough space for the overlay entries declared in the header. - const auto overlay_entries = reinterpret_cast<const Idmap_overlay_entry*>(data_ptr); - if (data_size / sizeof(Idmap_overlay_entry) < - static_cast<size_t>(dtohl(data_header->overlay_entry_count))) { - LOG(ERROR) << StringPrintf("Idmap too small for the number of overlay entries (%d)", - (int)dtohl(data_header->overlay_entry_count)); + std::optional<std::string_view> overlay_path = ReadString(&data_ptr, &data_size, "overlay path"); + if (!overlay_path) { return {}; } - - // Advance the data pointer past the overlay entries. - const size_t overlay_entry_size_bytes = - (dtohl(data_header->overlay_entry_count) * sizeof(Idmap_overlay_entry)); - data_ptr += overlay_entry_size_bytes; - data_size -= overlay_entry_size_bytes; - - // Read the idmap string pool that holds the value of inline string entries. - uint32_t string_pool_size = dtohl(*reinterpret_cast<const uint32_t*>(data_ptr)); - data_ptr += sizeof(uint32_t); - data_size -= sizeof(uint32_t); - - if (data_size < string_pool_size) { - LOG(ERROR) << StringPrintf("Idmap too small for string pool (length %d)", - (int)string_pool_size); + std::optional<std::string_view> target_path = ReadString(&data_ptr, &data_size, "target path"); + if (!target_path) { + return {}; + } + if (!ReadString(&data_ptr, &data_size, "target name") || + !ReadString(&data_ptr, &data_size, "debug info")) { return {}; } + // Parse the idmap data blocks. Currently idmap2 can only generate one data block. + auto data_header = ReadType<Idmap_data_header>(&data_ptr, &data_size, "data header"); + if (data_header == nullptr) { + return {}; + } + auto target_entries = ReadType<Idmap_target_entry>(&data_ptr, &data_size, "target", + dtohl(data_header->target_entry_count)); + if (target_entries == nullptr) { + return {}; + } + auto target_inline_entries = ReadType<Idmap_target_entry_inline>( + &data_ptr, &data_size, "target inline", dtohl(data_header->target_inline_entry_count)); + if (target_inline_entries == nullptr) { + return {}; + } + auto overlay_entries = ReadType<Idmap_overlay_entry>(&data_ptr, &data_size, "target inline", + dtohl(data_header->overlay_entry_count)); + if (overlay_entries == nullptr) { + return {}; + } + std::optional<std::string_view> string_pool = ReadString(&data_ptr, &data_size, "string pool"); + if (!string_pool) { + return {}; + } auto idmap_string_pool = util::make_unique<ResStringPool>(); - if (string_pool_size > 0) { - status_t err = idmap_string_pool->setTo(data_ptr, string_pool_size); + if (!string_pool->empty()) { + const status_t err = idmap_string_pool->setTo(string_pool->data(), string_pool->size()); if (err != NO_ERROR) { LOG(ERROR) << "idmap string pool corrupt."; return {}; @@ -296,12 +327,10 @@ std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_pa } // Can't use make_unique because LoadedIdmap constructor is private. - auto loaded_idmap = std::unique_ptr<LoadedIdmap>( - new LoadedIdmap(idmap_path.to_string(), getFileModDate(idmap_path.data()), header, - data_header, target_entries, target_inline_entries, overlay_entries, - idmap_string_pool.release())); - - return std::move(loaded_idmap); + return std::unique_ptr<LoadedIdmap>( + new LoadedIdmap(idmap_path.to_string(), header, data_header, target_entries, + target_inline_entries, overlay_entries, std::move(idmap_string_pool), + *target_path, *overlay_path)); } bool LoadedIdmap::IsUpToDate() const { diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index fdab03ba2de4..fd9a8d13e0c6 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -31,6 +31,11 @@ namespace android { class LoadedIdmap; class IdmapResMap; +struct Idmap_header; +struct Idmap_data_header; +struct Idmap_target_entry; +struct Idmap_target_entry_inline; +struct Idmap_overlay_entry; // A string pool for overlay apk assets. The string pool holds the strings of the overlay resources // table and additionally allows for loading strings from the idmap string pool. The idmap string @@ -148,29 +153,29 @@ class LoadedIdmap { const StringPiece& idmap_data); // Returns the path to the IDMAP. - inline const std::string& IdmapPath() const { + std::string_view IdmapPath() const { return idmap_path_; } // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated. - inline const std::string& OverlayApkPath() const { + std::string_view OverlayApkPath() const { return overlay_apk_path_; } // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated. - inline const std::string& TargetApkPath() const { + std::string_view TargetApkPath() const { return target_apk_path_; } // Returns a mapping from target resource ids to overlay values. - inline const IdmapResMap GetTargetResourcesMap( + const IdmapResMap GetTargetResourcesMap( uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table) const { return IdmapResMap(data_header_, target_entries_, target_inline_entries_, target_assigned_package_id, overlay_ref_table); } // Returns a dynamic reference table for a loaded overlay package. - inline const OverlayDynamicRefTable GetOverlayDynamicRefTable( + const OverlayDynamicRefTable GetOverlayDynamicRefTable( uint8_t target_assigned_package_id) const { return OverlayDynamicRefTable(data_header_, overlay_entries_, target_assigned_package_id); } @@ -190,22 +195,23 @@ class LoadedIdmap { const Idmap_overlay_entry* overlay_entries_; const std::unique_ptr<ResStringPool> string_pool_; - const std::string idmap_path_; - std::string overlay_apk_path_; - std::string target_apk_path_; - const time_t idmap_last_mod_time_; + std::string idmap_path_; + std::string_view overlay_apk_path_; + std::string_view target_apk_path_; + time_t idmap_last_mod_time_; private: DISALLOW_COPY_AND_ASSIGN(LoadedIdmap); explicit LoadedIdmap(std::string&& idmap_path, - time_t last_mod_time, const Idmap_header* header, const Idmap_data_header* data_header, const Idmap_target_entry* target_entries, const Idmap_target_entry_inline* target_inline_entries, const Idmap_overlay_entry* overlay_entries, - ResStringPool* string_pool); + std::unique_ptr<ResStringPool>&& string_pool, + std::string_view overlay_apk_path, + std::string_view target_apk_path); friend OverlayStringPool; }; diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index fb5f86473189..bfd564c258ee 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -44,7 +44,7 @@ namespace android { constexpr const static uint32_t kIdmapMagic = 0x504D4449u; -constexpr const static uint32_t kIdmapCurrentVersion = 0x00000005u; +constexpr const static uint32_t kIdmapCurrentVersion = 0x00000007u; /** * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of @@ -1700,56 +1700,6 @@ inline ResTable_overlayable_policy_header::PolicyFlags& operator |=( return first; } -struct Idmap_header { - // Always 0x504D4449 ('IDMP') - uint32_t magic; - - uint32_t version; - - uint32_t target_crc32; - uint32_t overlay_crc32; - - uint32_t fulfilled_policies; - uint32_t enforce_overlayable; - - uint8_t target_path[256]; - uint8_t overlay_path[256]; - - uint32_t debug_info_size; - uint8_t debug_info[0]; - - size_t Size() const; -}; - -struct Idmap_data_header { - uint8_t target_package_id; - uint8_t overlay_package_id; - - // Padding to ensure 4 byte alignment for target_entry_count - uint16_t p0; - - uint32_t target_entry_count; - uint32_t target_inline_entry_count; - uint32_t overlay_entry_count; - - uint32_t string_pool_index_offset; -}; - -struct Idmap_target_entry { - uint32_t target_id; - uint32_t overlay_id; -}; - -struct Idmap_target_entry_inline { - uint32_t target_id; - Res_value value; -}; - -struct Idmap_overlay_entry { - uint32_t overlay_id; - uint32_t target_id; -}; - class AssetManager2; /** diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h index aceeeccccb61..c59b5b6c51a2 100644 --- a/libs/androidfw/include/androidfw/Util.h +++ b/libs/androidfw/include/androidfw/Util.h @@ -128,10 +128,14 @@ std::string Utf16ToUtf8(const StringPiece16& utf16); std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep); template <typename T> -bool IsFourByteAligned(const incfs::map_ptr<T>& data) { +inline bool IsFourByteAligned(const incfs::map_ptr<T>& data) { return ((size_t)data.unsafe_ptr() & 0x3U) == 0; } +inline bool IsFourByteAligned(const void* data) { + return ((size_t)data & 0x3U) == 0; +} + } // namespace util } // namespace android diff --git a/libs/androidfw/tests/data/app/app.apk b/libs/androidfw/tests/data/app/app.apk Binary files differindex c8ad86ded851..67036959d185 100644 --- a/libs/androidfw/tests/data/app/app.apk +++ b/libs/androidfw/tests/data/app/app.apk diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap Binary files differindex 3ab244eb084a..723413c3cea8 100644 --- a/libs/androidfw/tests/data/overlay/overlay.idmap +++ b/libs/androidfw/tests/data/overlay/overlay.idmap diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index acb74f415f41..815ffdeade45 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -814,6 +814,18 @@ void SkiaCanvas::drawCircle(uirenderer::CanvasPropertyPrimitive* x, mCanvas->drawDrawable(drawable.get()); } +void SkiaCanvas::drawRipple(uirenderer::CanvasPropertyPrimitive* x, + uirenderer::CanvasPropertyPrimitive* y, + uirenderer::CanvasPropertyPrimitive* radius, + uirenderer::CanvasPropertyPaint* paint, + uirenderer::CanvasPropertyPrimitive* progress, + sk_sp<SkRuntimeEffect> runtimeEffect) { + sk_sp<uirenderer::skiapipeline::AnimatedRipple> drawable( + new uirenderer::skiapipeline::AnimatedRipple(x, y, radius, paint, progress, + runtimeEffect)); + mCanvas->drawDrawable(drawable.get()); +} + void SkiaCanvas::drawPicture(const SkPicture& picture) { // TODO: Change to mCanvas->drawPicture()? SkCanvas::drawPicture seems to be // where the logic is for playback vs. ref picture. Using picture.playback here diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index 584321ec9094..fa7d373308d3 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -147,6 +147,12 @@ public: uirenderer::CanvasPropertyPrimitive* y, uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint) override; + virtual void drawRipple(uirenderer::CanvasPropertyPrimitive* x, + uirenderer::CanvasPropertyPrimitive* y, + uirenderer::CanvasPropertyPrimitive* radius, + uirenderer::CanvasPropertyPaint* paint, + uirenderer::CanvasPropertyPrimitive* progress, + sk_sp<SkRuntimeEffect> runtimeEffect) override; virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override; virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override; diff --git a/libs/hwui/canvas/CanvasOpTypes.h b/libs/hwui/canvas/CanvasOpTypes.h index f0aa7774a6cc..cde50bd66a15 100644 --- a/libs/hwui/canvas/CanvasOpTypes.h +++ b/libs/hwui/canvas/CanvasOpTypes.h @@ -42,6 +42,7 @@ enum class CanvasOpType : int8_t { DrawRoundRectProperty, DrawDoubleRoundRect, DrawCircleProperty, + DrawRippleProperty, DrawCircle, DrawOval, DrawArc, diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h index 62c26c7b6f6a..ea9fea974d06 100644 --- a/libs/hwui/canvas/CanvasOps.h +++ b/libs/hwui/canvas/CanvasOps.h @@ -23,6 +23,7 @@ #include <SkVertices.h> #include <SkImage.h> #include <SkPicture.h> +#include <SkRuntimeEffect.h> #include <hwui/Bitmap.h> #include <log/log.h> #include "CanvasProperty.h" @@ -142,6 +143,42 @@ struct CanvasOp<CanvasOpType::DrawCircleProperty> { ASSERT_DRAWABLE() }; +template<> +struct CanvasOp<CanvasOpType::DrawRippleProperty> { + sp<uirenderer::CanvasPropertyPrimitive> x; + sp<uirenderer::CanvasPropertyPrimitive> y; + sp<uirenderer::CanvasPropertyPrimitive> radius; + sp<uirenderer::CanvasPropertyPaint> paint; + sp<uirenderer::CanvasPropertyPrimitive> progress; + sk_sp<SkRuntimeEffect> effect; + + void draw(SkCanvas* canvas) const { + SkRuntimeShaderBuilder runtimeEffectBuilder(effect); + + SkRuntimeShaderBuilder::BuilderUniform center = runtimeEffectBuilder.uniform("in_origin"); + if (center.fVar != nullptr) { + center = SkV2{x->value, y->value}; + } + + SkRuntimeShaderBuilder::BuilderUniform radiusU = + runtimeEffectBuilder.uniform("in_maxRadius"); + if (radiusU.fVar != nullptr) { + radiusU = radius->value; + } + + SkRuntimeShaderBuilder::BuilderUniform progressU = + runtimeEffectBuilder.uniform("in_progress"); + if (progressU.fVar != nullptr) { + progressU = progress->value; + } + + SkPaint paintMod = paint->value; + paintMod.setShader(runtimeEffectBuilder.makeShader(nullptr, false)); + canvas->drawCircle(x->value, y->value, radius->value, paintMod); + } + ASSERT_DRAWABLE() +}; + template <> struct CanvasOp<CanvasOpType::DrawColor> { SkColor4f color; diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 11fa3223a9c8..d0c996bee449 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -31,6 +31,7 @@ class SkAnimatedImage; class SkCanvasState; +class SkRuntimeEffect; class SkVertices; namespace minikin { @@ -133,6 +134,12 @@ public: uirenderer::CanvasPropertyPrimitive* y, uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint) = 0; + virtual void drawRipple(uirenderer::CanvasPropertyPrimitive* x, + uirenderer::CanvasPropertyPrimitive* y, + uirenderer::CanvasPropertyPrimitive* radius, + uirenderer::CanvasPropertyPaint* paint, + uirenderer::CanvasPropertyPrimitive* progress, + sk_sp<SkRuntimeEffect> runtimeEffect) = 0; virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) = 0; virtual void drawRenderNode(uirenderer::RenderNode* renderNode) = 0; diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index 688913471913..b3f7627b1cf7 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -17,11 +17,19 @@ #include "ImageDecoder.h" #include <hwui/Bitmap.h> +#include <log/log.h> #include <SkAndroidCodec.h> +#include <SkBitmap.h> +#include <SkBlendMode.h> #include <SkCanvas.h> +#include <SkEncodedOrigin.h> +#include <SkFilterQuality.h> #include <SkPaint.h> +#undef LOG_TAG +#define LOG_TAG "ImageDecoder" + using namespace android; sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const { @@ -44,17 +52,29 @@ ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChu , mOutColorType(mCodec->computeOutputColorType(kN32_SkColorType)) , mUnpremultipliedRequired(false) , mOutColorSpace(getDefaultColorSpace()) - , mSampleSize(1) { mTargetSize = swapWidthHeight() ? SkISize { mDecodeSize.height(), mDecodeSize.width() } : mDecodeSize; + this->rewind(); } +ImageDecoder::~ImageDecoder() = default; + SkAlphaType ImageDecoder::getOutAlphaType() const { return opaque() ? kOpaque_SkAlphaType : mUnpremultipliedRequired ? kUnpremul_SkAlphaType : kPremul_SkAlphaType; } +static SkISize swapped(const SkISize& size) { + return SkISize { size.height(), size.width() }; +} + +static bool requires_matrix_scaling(bool swapWidthHeight, const SkISize& decodeSize, + const SkISize& targetSize) { + return (swapWidthHeight && decodeSize != swapped(targetSize)) + || (!swapWidthHeight && decodeSize != targetSize); +} + bool ImageDecoder::setTargetSize(int width, int height) { if (width <= 0 || height <= 0) { return false; @@ -78,17 +98,21 @@ bool ImageDecoder::setTargetSize(int width, int height) { } } - SkISize targetSize = { width, height }; - SkISize decodeSize = swapWidthHeight() ? SkISize { height, width } : targetSize; + const bool swap = swapWidthHeight(); + const SkISize targetSize = { width, height }; + SkISize decodeSize = swap ? SkISize { height, width } : targetSize; int sampleSize = mCodec->computeSampleSize(&decodeSize); - if (decodeSize != targetSize && mUnpremultipliedRequired && !opaque()) { - return false; + if (mUnpremultipliedRequired && !opaque()) { + // Allow using a matrix to handle orientation, but not scaling. + if (requires_matrix_scaling(swap, decodeSize, targetSize)) { + return false; + } } mTargetSize = targetSize; mDecodeSize = decodeSize; - mSampleSize = sampleSize; + mOptions.fSampleSize = sampleSize; return true; } @@ -137,8 +161,10 @@ bool ImageDecoder::setOutColorType(SkColorType colorType) { } bool ImageDecoder::setUnpremultipliedRequired(bool required) { - if (required && !opaque() && mDecodeSize != mTargetSize) { - return false; + if (required && !opaque()) { + if (requires_matrix_scaling(swapWidthHeight(), mDecodeSize, mTargetSize)) { + return false; + } } mUnpremultipliedRequired = required; return true; @@ -176,26 +202,202 @@ int ImageDecoder::height() const { } bool ImageDecoder::opaque() const { - return mCodec->getInfo().alphaType() == kOpaque_SkAlphaType; + return mCurrentFrameIsOpaque; } bool ImageDecoder::gray() const { return mCodec->getInfo().colorType() == kGray_8_SkColorType; } +bool ImageDecoder::isAnimated() { + return mCodec->codec()->getFrameCount() > 1; +} + +int ImageDecoder::currentFrame() const { + return mOptions.fFrameIndex; +} + +bool ImageDecoder::rewind() { + mOptions.fFrameIndex = 0; + mOptions.fPriorFrame = SkCodec::kNoFrame; + mCurrentFrameIsIndependent = true; + mCurrentFrameIsOpaque = mCodec->getInfo().isOpaque(); + mRestoreState = RestoreState::kDoNothing; + mRestoreFrame = nullptr; + + // TODO: Rewind the input now instead of in the next call to decode, and + // plumb through whether rewind succeeded. + return true; +} + +bool ImageDecoder::advanceFrame() { + const int frameIndex = ++mOptions.fFrameIndex; + const int frameCount = mCodec->codec()->getFrameCount(); + if (frameIndex >= frameCount) { + // Prevent overflow from repeated calls to advanceFrame. + mOptions.fFrameIndex = frameCount; + return false; + } + + SkCodec::FrameInfo frameInfo; + if (!mCodec->codec()->getFrameInfo(frameIndex, &frameInfo) + || !frameInfo.fFullyReceived) { + // Mark the decoder as finished, requiring a rewind. + mOptions.fFrameIndex = frameCount; + return false; + } + + mCurrentFrameIsIndependent = frameInfo.fRequiredFrame == SkCodec::kNoFrame; + mCurrentFrameIsOpaque = frameInfo.fAlphaType == kOpaque_SkAlphaType; + + if (frameInfo.fDisposalMethod == SkCodecAnimation::DisposalMethod::kRestorePrevious) { + switch (mRestoreState) { + case RestoreState::kDoNothing: + case RestoreState::kNeedsRestore: + mRestoreState = RestoreState::kFirstRPFrame; + break; + case RestoreState::kFirstRPFrame: + mRestoreState = RestoreState::kRPFrame; + break; + case RestoreState::kRPFrame: + // Unchanged. + break; + } + } else { // New frame is not restore previous + switch (mRestoreState) { + case RestoreState::kFirstRPFrame: + case RestoreState::kRPFrame: + mRestoreState = RestoreState::kNeedsRestore; + break; + case RestoreState::kNeedsRestore: + mRestoreState = RestoreState::kDoNothing; + mRestoreFrame = nullptr; + [[fallthrough]]; + case RestoreState::kDoNothing: + mOptions.fPriorFrame = frameIndex - 1; + break; + } + } + + return true; +} + +SkCodec::FrameInfo ImageDecoder::getCurrentFrameInfo() { + LOG_ALWAYS_FATAL_IF(finished()); + + auto dims = mCodec->codec()->dimensions(); + SkCodec::FrameInfo info; + if (!mCodec->codec()->getFrameInfo(mOptions.fFrameIndex, &info)) { + // SkCodec may return false for a non-animated image. Provide defaults. + info.fRequiredFrame = SkCodec::kNoFrame; + info.fDuration = 0; + info.fFullyReceived = true; + info.fAlphaType = mCodec->codec()->getInfo().alphaType(); + info.fHasAlphaWithinBounds = info.fAlphaType != kOpaque_SkAlphaType; + info.fDisposalMethod = SkCodecAnimation::DisposalMethod::kKeep; + info.fBlend = SkCodecAnimation::Blend::kSrc; + info.fFrameRect = SkIRect::MakeSize(dims); + } + + if (auto origin = mCodec->codec()->getOrigin(); origin != kDefault_SkEncodedOrigin) { + if (SkEncodedOriginSwapsWidthHeight(origin)) { + dims = swapped(dims); + } + auto matrix = SkEncodedOriginToMatrix(origin, dims.width(), dims.height()); + auto rect = SkRect::Make(info.fFrameRect); + LOG_ALWAYS_FATAL_IF(!matrix.mapRect(&rect)); + rect.roundIn(&info.fFrameRect); + } + return info; +} + +bool ImageDecoder::finished() const { + return mOptions.fFrameIndex >= mCodec->codec()->getFrameCount(); +} + SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { + // This was checked inside setTargetSize, but it's possible the first frame + // was opaque, so that method succeeded, but after calling advanceFrame, the + // current frame is not opaque. + if (mUnpremultipliedRequired && !opaque()) { + // Allow using a matrix to handle orientation, but not scaling. + if (requires_matrix_scaling(swapWidthHeight(), mDecodeSize, mTargetSize)) { + return SkCodec::kInvalidScale; + } + } + void* decodePixels = pixels; size_t decodeRowBytes = rowBytes; - auto decodeInfo = SkImageInfo::Make(mDecodeSize, mOutColorType, getOutAlphaType(), - getOutputColorSpace()); + const auto decodeInfo = SkImageInfo::Make(mDecodeSize, mOutColorType, getOutAlphaType(), + getOutputColorSpace()); + const auto outputInfo = getOutputInfo(); + switch (mRestoreState) { + case RestoreState::kFirstRPFrame:{ + // This frame is marked kRestorePrevious. The prior frame should be in + // |pixels|, and it is what we'll restore after each consecutive + // kRestorePrevious frame. Cache it now. + if (!(mRestoreFrame = Bitmap::allocateHeapBitmap(outputInfo))) { + return SkCodec::kInternalError; + } + + const uint8_t* srcRow = static_cast<uint8_t*>(pixels); + uint8_t* dstRow = static_cast<uint8_t*>(mRestoreFrame->pixels()); + for (int y = 0; y < outputInfo.height(); y++) { + memcpy(dstRow, srcRow, outputInfo.minRowBytes()); + srcRow += rowBytes; + dstRow += mRestoreFrame->rowBytes(); + } + break; + } + case RestoreState::kRPFrame: + case RestoreState::kNeedsRestore: + // Restore the cached frame. It's possible that the client skipped decoding a frame, so + // we never cached it. + if (mRestoreFrame) { + const uint8_t* srcRow = static_cast<uint8_t*>(mRestoreFrame->pixels()); + uint8_t* dstRow = static_cast<uint8_t*>(pixels); + for (int y = 0; y < outputInfo.height(); y++) { + memcpy(dstRow, srcRow, outputInfo.minRowBytes()); + srcRow += mRestoreFrame->rowBytes(); + dstRow += rowBytes; + } + } + break; + case RestoreState::kDoNothing: + break; + } + // Used if we need a temporary before scaling or subsetting. // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380. SkBitmap tmp; const bool scale = mDecodeSize != mTargetSize; const auto origin = mCodec->codec()->getOrigin(); const bool handleOrigin = origin != kDefault_SkEncodedOrigin; + SkMatrix outputMatrix; if (scale || handleOrigin || mCropRect) { - if (!tmp.setInfo(decodeInfo)) { + if (mCropRect) { + outputMatrix.setTranslate(-mCropRect->fLeft, -mCropRect->fTop); + } + + int targetWidth = mTargetSize.width(); + int targetHeight = mTargetSize.height(); + if (handleOrigin) { + outputMatrix.preConcat(SkEncodedOriginToMatrix(origin, targetWidth, targetHeight)); + if (SkEncodedOriginSwapsWidthHeight(origin)) { + std::swap(targetWidth, targetHeight); + } + } + if (scale) { + float scaleX = (float) targetWidth / mDecodeSize.width(); + float scaleY = (float) targetHeight / mDecodeSize.height(); + outputMatrix.preScale(scaleX, scaleY); + } + // It's possible that this portion *does* have alpha, even if the + // composed frame does not. In that case, the SkBitmap needs to have + // alpha so it blends properly. + if (!tmp.setInfo(decodeInfo.makeAlphaType(mUnpremultipliedRequired ? kUnpremul_SkAlphaType + : kPremul_SkAlphaType))) + { return SkCodec::kInternalError; } if (!Bitmap::allocateHeapBitmap(&tmp)) { @@ -203,15 +405,28 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { } decodePixels = tmp.getPixels(); decodeRowBytes = tmp.rowBytes(); + + if (!mCurrentFrameIsIndependent) { + SkMatrix inverse; + if (outputMatrix.invert(&inverse)) { + SkCanvas canvas(tmp, SkCanvas::ColorBehavior::kLegacy); + canvas.setMatrix(inverse); + SkPaint paint; + paint.setFilterQuality(kLow_SkFilterQuality); // bilinear + SkBitmap priorFrame; + priorFrame.installPixels(outputInfo, pixels, rowBytes); + canvas.drawBitmap(priorFrame, 0, 0, &paint); + } else { + ALOGE("Failed to invert matrix!"); + } + } } - SkAndroidCodec::AndroidOptions options; - options.fSampleSize = mSampleSize; - auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &options); + auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &mOptions); if (scale || handleOrigin || mCropRect) { SkBitmap scaledBm; - if (!scaledBm.installPixels(getOutputInfo(), pixels, rowBytes)) { + if (!scaledBm.installPixels(outputInfo, pixels, rowBytes)) { return SkCodec::kInternalError; } @@ -220,26 +435,6 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy); - SkMatrix outputMatrix; - if (mCropRect) { - outputMatrix.setTranslate(-mCropRect->fLeft, -mCropRect->fTop); - } - - int targetWidth = mTargetSize.width(); - int targetHeight = mTargetSize.height(); - if (handleOrigin) { - outputMatrix.preConcat(SkEncodedOriginToMatrix(origin, targetWidth, targetHeight)); - if (SkEncodedOriginSwapsWidthHeight(origin)) { - std::swap(targetWidth, targetHeight); - } - } - - if (scale) { - float scaleX = (float) targetWidth / mDecodeSize.width(); - float scaleY = (float) targetHeight / mDecodeSize.height(); - outputMatrix.preScale(scaleX, scaleY); - } - canvas.setMatrix(outputMatrix); canvas.drawBitmap(tmp, 0.0f, 0.0f, &paint); } diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h index a08e92478fb0..90261b16e57e 100644 --- a/libs/hwui/hwui/ImageDecoder.h +++ b/libs/hwui/hwui/ImageDecoder.h @@ -15,6 +15,7 @@ */ #pragma once +#include <SkAndroidCodec.h> #include <SkCodec.h> #include <SkImageInfo.h> #include <SkPngChunkReader.h> @@ -24,17 +25,18 @@ #include <optional> -class SkAndroidCodec; - namespace android { -class ANDROID_API ImageDecoder { +class Bitmap; + +class ANDROID_API ImageDecoder final { public: std::unique_ptr<SkAndroidCodec> mCodec; sk_sp<SkPngChunkReader> mPeeker; ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker = nullptr); + ~ImageDecoder(); bool setTargetSize(int width, int height); bool setCropRect(const SkIRect*); @@ -46,25 +48,65 @@ public: sk_sp<SkColorSpace> getDefaultColorSpace() const; void setOutColorSpace(sk_sp<SkColorSpace> cs); - // The size is the final size after scaling and cropping. + // The size is the final size after scaling, adjusting for the origin, and + // cropping. SkImageInfo getOutputInfo() const; int width() const; int height() const; + // True if the current frame is opaque. bool opaque() const; bool gray() const; SkCodec::Result decode(void* pixels, size_t rowBytes); + // Return true if the decoder has advanced beyond all frames. + bool finished() const; + + bool advanceFrame(); + bool rewind(); + + bool isAnimated(); + int currentFrame() const; + + SkCodec::FrameInfo getCurrentFrameInfo(); + private: + // State machine for keeping track of how to handle RestorePrevious (RP) + // frames in decode(). + enum class RestoreState { + // Neither this frame nor the prior is RP, so there is no need to cache + // or restore. + kDoNothing, + + // This is the first in a sequence of one or more RP frames. decode() + // needs to cache the provided pixels. + kFirstRPFrame, + + // This is the second (or later) in a sequence of multiple RP frames. + // decode() needs to restore the cached frame that preceded the first RP + // frame in the sequence. + kRPFrame, + + // This is the first non-RP frame after a sequence of one or more RP + // frames. decode() still needs to restore the cached frame. Separate + // from kRPFrame because if the following frame is RP the state will + // change to kFirstRPFrame. + kNeedsRestore, + }; + SkISize mTargetSize; SkISize mDecodeSize; SkColorType mOutColorType; bool mUnpremultipliedRequired; sk_sp<SkColorSpace> mOutColorSpace; - int mSampleSize; + SkAndroidCodec::AndroidOptions mOptions; + bool mCurrentFrameIsIndependent; + bool mCurrentFrameIsOpaque; + RestoreState mRestoreState; + sk_sp<Bitmap> mRestoreFrame; std::optional<SkIRect> mCropRect; ImageDecoder(const ImageDecoder&) = delete; diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp index 7c1422de0984..f4877f4a4fc4 100644 --- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp +++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp @@ -20,8 +20,8 @@ #include <utils/Looper.h> #endif -#include <SkBitmap.h> #include <SkRegion.h> +#include <SkRuntimeEffect.h> #include <Rect.h> #include <RenderNode.h> @@ -139,6 +139,21 @@ static void android_view_DisplayListCanvas_drawCircleProps(CRITICAL_JNI_PARAMS_C canvas->drawCircle(xProp, yProp, radiusProp, paintProp); } +static void android_view_DisplayListCanvas_drawRippleProps(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, + jlong xPropPtr, jlong yPropPtr, + jlong radiusPropPtr, jlong paintPropPtr, + jlong progressPropPtr, jlong effectPtr) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr); + CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr); + CanvasPropertyPrimitive* radiusProp = reinterpret_cast<CanvasPropertyPrimitive*>(radiusPropPtr); + CanvasPropertyPaint* paintProp = reinterpret_cast<CanvasPropertyPaint*>(paintPropPtr); + CanvasPropertyPrimitive* progressProp = + reinterpret_cast<CanvasPropertyPrimitive*>(progressPropPtr); + SkRuntimeEffect* effect = reinterpret_cast<SkRuntimeEffect*>(effectPtr); + canvas->drawRipple(xProp, yProp, radiusProp, paintProp, progressProp, sk_ref_sp(effect)); +} + static void android_view_DisplayListCanvas_drawWebViewFunctor(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jint functor) { Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); canvas->drawWebViewFunctor(functor); @@ -163,6 +178,7 @@ static JNINativeMethod gMethods[] = { { "nDrawCircle", "(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps }, { "nDrawRoundRect", "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps }, { "nDrawWebViewFunctor", "(JI)V", (void*) android_view_DisplayListCanvas_drawWebViewFunctor }, + { "nDrawRipple", "(JJJJJJJ)V", (void*) android_view_DisplayListCanvas_drawRippleProps }, }; int register_android_view_DisplayListCanvas(JNIEnv* env) { diff --git a/libs/hwui/pipeline/skia/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h index bf19655825b3..3142d927d4c4 100644 --- a/libs/hwui/pipeline/skia/AnimatedDrawables.h +++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h @@ -18,6 +18,7 @@ #include <SkCanvas.h> #include <SkDrawable.h> +#include <SkRuntimeEffect.h> #include <utils/RefBase.h> #include "CanvasProperty.h" @@ -54,6 +55,59 @@ private: sp<uirenderer::CanvasPropertyPaint> mPaint; }; +class AnimatedRipple : public SkDrawable { +public: + AnimatedRipple(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y, + uirenderer::CanvasPropertyPrimitive* radius, + uirenderer::CanvasPropertyPaint* paint, + uirenderer::CanvasPropertyPrimitive* progress, + sk_sp<SkRuntimeEffect> runtimeEffect) + : mX(x) + , mY(y) + , mRadius(radius) + , mPaint(paint) + , mProgress(progress) + , mRuntimeEffectBuilder(std::move(runtimeEffect)) {} + +protected: + virtual SkRect onGetBounds() override { + const float x = mX->value; + const float y = mY->value; + const float radius = mRadius->value; + return SkRect::MakeLTRB(x - radius, y - radius, x + radius, y + radius); + } + virtual void onDraw(SkCanvas* canvas) override { + SkRuntimeShaderBuilder::BuilderUniform center = mRuntimeEffectBuilder.uniform("in_origin"); + if (center.fVar != nullptr) { + center = SkV2{mX->value, mY->value}; + } + + SkRuntimeShaderBuilder::BuilderUniform radiusU = + mRuntimeEffectBuilder.uniform("in_maxRadius"); + if (radiusU.fVar != nullptr) { + radiusU = mRadius->value; + } + + SkRuntimeShaderBuilder::BuilderUniform progressU = + mRuntimeEffectBuilder.uniform("in_progress"); + if (progressU.fVar != nullptr) { + progressU = mProgress->value; + } + + SkPaint paint = mPaint->value; + paint.setShader(mRuntimeEffectBuilder.makeShader(nullptr, false)); + canvas->drawCircle(mX->value, mY->value, mRadius->value, paint); + } + +private: + sp<uirenderer::CanvasPropertyPrimitive> mX; + sp<uirenderer::CanvasPropertyPrimitive> mY; + sp<uirenderer::CanvasPropertyPrimitive> mRadius; + sp<uirenderer::CanvasPropertyPaint> mPaint; + sp<uirenderer::CanvasPropertyPrimitive> mProgress; + SkRuntimeShaderBuilder mRuntimeEffectBuilder; +}; + class AnimatedCircle : public SkDrawable { public: AnimatedCircle(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y, diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index a43627803e71..7faebda8e043 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -85,6 +85,16 @@ void SkiaRecordingCanvas::drawCircle(uirenderer::CanvasPropertyPrimitive* x, drawDrawable(mDisplayList->allocateDrawable<AnimatedCircle>(x, y, radius, paint)); } +void SkiaRecordingCanvas::drawRipple(uirenderer::CanvasPropertyPrimitive* x, + uirenderer::CanvasPropertyPrimitive* y, + uirenderer::CanvasPropertyPrimitive* radius, + uirenderer::CanvasPropertyPaint* paint, + uirenderer::CanvasPropertyPrimitive* progress, + sk_sp<SkRuntimeEffect> runtimeEffect) { + drawDrawable(mDisplayList->allocateDrawable<AnimatedRipple>(x, y, radius, paint, progress, + runtimeEffect)); +} + void SkiaRecordingCanvas::enableZ(bool enableZ) { if (mCurrentBarrier && enableZ) { // Already in a re-order section, nothing to do diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index 83e934974afd..622df43b0528 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -66,6 +66,12 @@ public: uirenderer::CanvasPropertyPrimitive* y, uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint) override; + virtual void drawRipple(uirenderer::CanvasPropertyPrimitive* x, + uirenderer::CanvasPropertyPrimitive* y, + uirenderer::CanvasPropertyPrimitive* radius, + uirenderer::CanvasPropertyPaint* paint, + uirenderer::CanvasPropertyPrimitive* progress, + sk_sp<SkRuntimeEffect> runtimeEffect) override; virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override; diff --git a/location/java/android/location/GnssAntennaInfo.java b/location/java/android/location/GnssAntennaInfo.java index 23977f18f749..f1eb8fccdd3c 100644 --- a/location/java/android/location/GnssAntennaInfo.java +++ b/location/java/android/location/GnssAntennaInfo.java @@ -34,17 +34,19 @@ import java.util.Objects; public final class GnssAntennaInfo implements Parcelable { private final double mCarrierFrequencyMHz; private final PhaseCenterOffset mPhaseCenterOffset; - private final SphericalCorrections mPhaseCenterVariationCorrections; - private final SphericalCorrections mSignalGainCorrections; + private final @Nullable SphericalCorrections mPhaseCenterVariationCorrections; + private final @Nullable SphericalCorrections mSignalGainCorrections; /** - * Used for receiving GNSS antenna info from the GNSS engine. You can implement this interface - * and call {@link LocationManager#registerAntennaInfoListener}; + * Used for receiving GNSS antenna info from the GNSS engine. + * + * @deprecated Prefer to use a broadcast receiver for + * {@link LocationManager#ACTION_GNSS_ANTENNA_INFOS_CHANGED}. */ + @Deprecated public interface Listener { /** - * Returns the latest GNSS antenna info. This event is triggered when a listener is - * registered, and whenever the antenna info changes (due to a device configuration change). + * Invoked on a change to GNSS antenna info. */ void onGnssAntennaInfoReceived(@NonNull List<GnssAntennaInfo> gnssAntennaInfos); } @@ -172,6 +174,28 @@ public final class GnssAntennaInfo implements Parcelable { + ", OffsetZMm=" + mOffsetZMm + " +/-" + mOffsetZUncertaintyMm + '}'; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PhaseCenterOffset)) { + return false; + } + PhaseCenterOffset that = (PhaseCenterOffset) o; + return Double.compare(that.mOffsetXMm, mOffsetXMm) == 0 + && Double.compare(that.mOffsetXUncertaintyMm, mOffsetXUncertaintyMm) == 0 + && Double.compare(that.mOffsetYMm, mOffsetYMm) == 0 + && Double.compare(that.mOffsetYUncertaintyMm, mOffsetYUncertaintyMm) == 0 + && Double.compare(that.mOffsetZMm, mOffsetZMm) == 0 + && Double.compare(that.mOffsetZUncertaintyMm, mOffsetZUncertaintyMm) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(mOffsetXMm, mOffsetYMm, mOffsetZMm); + } } /** @@ -190,38 +214,34 @@ public final class GnssAntennaInfo implements Parcelable { * i.e., deltaPhi = 180 / (number of columns - 1). */ public static final class SphericalCorrections implements Parcelable { - private final double[][] mCorrections; - private final double[][] mCorrectionUncertainties; - private final double mDeltaTheta; - private final double mDeltaPhi; + private final int mNumRows; private final int mNumColumns; + private final double[][] mCorrections; + private final double[][] mCorrectionUncertainties; public SphericalCorrections(@NonNull double[][] corrections, @NonNull double[][] correctionUncertainties) { - if (corrections.length != correctionUncertainties.length - || corrections[0].length != correctionUncertainties[0].length) { - throw new IllegalArgumentException("Correction and correction uncertainty arrays " - + "must have the same dimensions."); + if (corrections.length != correctionUncertainties.length || corrections.length < 1) { + throw new IllegalArgumentException("correction and uncertainty arrays must have " + + "the same (non-zero) dimensions"); } mNumRows = corrections.length; - if (mNumRows < 1) { - throw new IllegalArgumentException("Arrays must have at least one row."); - } - mNumColumns = corrections[0].length; - if (mNumColumns < 2) { - throw new IllegalArgumentException("Arrays must have at least two columns."); + for (int i = 0; i < corrections.length; i++) { + if (corrections[i].length != mNumColumns + || correctionUncertainties[i].length != mNumColumns || mNumColumns < 2) { + throw new IllegalArgumentException("correction and uncertainty arrays must all " + + " have the same (greater than 2) number of columns"); + } } mCorrections = corrections; mCorrectionUncertainties = correctionUncertainties; - mDeltaTheta = 360.0d / mNumRows; - mDeltaPhi = 180.0d / (mNumColumns - 1); } - SphericalCorrections(Parcel in) { + private SphericalCorrections(Parcel in) { int numRows = in.readInt(); int numColumns = in.readInt(); @@ -231,19 +251,16 @@ public final class GnssAntennaInfo implements Parcelable { new double[numRows][numColumns]; for (int row = 0; row < numRows; row++) { - in.readDoubleArray(corrections[row]); - } - - for (int row = 0; row < numRows; row++) { - in.readDoubleArray(correctionUncertainties[row]); + for (int col = 0; col < numColumns; col++) { + corrections[row][col] = in.readDouble(); + correctionUncertainties[row][col] = in.readDouble(); + } } mNumRows = numRows; mNumColumns = numColumns; mCorrections = corrections; mCorrectionUncertainties = correctionUncertainties; - mDeltaTheta = 360.0d / mNumRows; - mDeltaPhi = 180.0d / (mNumColumns - 1); } /** @@ -286,7 +303,7 @@ public final class GnssAntennaInfo implements Parcelable { */ @FloatRange(from = 0.0f, to = 360.0f) public double getDeltaTheta() { - return mDeltaTheta; + return 360.0D / mNumRows; } /** @@ -294,7 +311,7 @@ public final class GnssAntennaInfo implements Parcelable { */ @FloatRange(from = 0.0f, to = 180.0f) public double getDeltaPhi() { - return mDeltaPhi; + return 180.0D / (mNumColumns - 1); } @@ -320,11 +337,11 @@ public final class GnssAntennaInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mNumRows); dest.writeInt(mNumColumns); - for (double[] row : mCorrections) { - dest.writeDoubleArray(row); - } - for (double[] row : mCorrectionUncertainties) { - dest.writeDoubleArray(row); + for (int row = 0; row < mNumRows; row++) { + for (int col = 0; col < mNumColumns; col++) { + dest.writeDouble(mCorrections[row][col]); + dest.writeDouble(mCorrectionUncertainties[row][col]); + } } } @@ -333,22 +350,41 @@ public final class GnssAntennaInfo implements Parcelable { return "SphericalCorrections{" + "Corrections=" + Arrays.toString(mCorrections) + ", CorrectionUncertainties=" + Arrays.toString(mCorrectionUncertainties) - + ", DeltaTheta=" + mDeltaTheta - + ", DeltaPhi=" + mDeltaPhi + + ", DeltaTheta=" + getDeltaTheta() + + ", DeltaPhi=" + getDeltaPhi() + '}'; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SphericalCorrections)) { + return false; + } + SphericalCorrections that = (SphericalCorrections) o; + return mNumRows == that.mNumRows + && mNumColumns == that.mNumColumns + && Arrays.deepEquals(mCorrections, that.mCorrections) + && Arrays.deepEquals(mCorrectionUncertainties, that.mCorrectionUncertainties); + } + + @Override + public int hashCode() { + int result = Arrays.deepHashCode(mCorrections); + result = 31 * result + Arrays.deepHashCode(mCorrectionUncertainties); + return result; + } } private GnssAntennaInfo( double carrierFrequencyMHz, - @NonNull PhaseCenterOffset phaseCenterOffset, + PhaseCenterOffset phaseCenterOffset, @Nullable SphericalCorrections phaseCenterVariationCorrections, @Nullable SphericalCorrections signalGainCorrectionDbi) { - if (phaseCenterOffset == null) { - throw new IllegalArgumentException("Phase Center Offset Coordinates cannot be null."); - } mCarrierFrequencyMHz = carrierFrequencyMHz; - mPhaseCenterOffset = phaseCenterOffset; + mPhaseCenterOffset = Objects.requireNonNull(phaseCenterOffset); mPhaseCenterVariationCorrections = phaseCenterVariationCorrections; mSignalGainCorrections = signalGainCorrectionDbi; } @@ -359,8 +395,28 @@ public final class GnssAntennaInfo implements Parcelable { public static class Builder { private double mCarrierFrequencyMHz; private PhaseCenterOffset mPhaseCenterOffset; - private SphericalCorrections mPhaseCenterVariationCorrections; - private SphericalCorrections mSignalGainCorrections; + private @Nullable SphericalCorrections mPhaseCenterVariationCorrections; + private @Nullable SphericalCorrections mSignalGainCorrections; + + /** + * @deprecated Prefer {@link #Builder(double, PhaseCenterOffset)}. + */ + @Deprecated + public Builder() { + this(0, new PhaseCenterOffset(0, 0, 0, 0, 0, 0)); + } + + public Builder(double carrierFrequencyMHz, @NonNull PhaseCenterOffset phaseCenterOffset) { + mCarrierFrequencyMHz = carrierFrequencyMHz; + mPhaseCenterOffset = Objects.requireNonNull(phaseCenterOffset); + } + + public Builder(@NonNull GnssAntennaInfo antennaInfo) { + mCarrierFrequencyMHz = antennaInfo.mCarrierFrequencyMHz; + mPhaseCenterOffset = antennaInfo.mPhaseCenterOffset; + mPhaseCenterVariationCorrections = antennaInfo.mPhaseCenterVariationCorrections; + mSignalGainCorrections = antennaInfo.mSignalGainCorrections; + } /** * Set antenna carrier frequency (MHz). @@ -462,32 +518,29 @@ public final class GnssAntennaInfo implements Parcelable { return mSignalGainCorrections; } - public static final @android.annotation.NonNull Creator<GnssAntennaInfo> CREATOR = - new Creator<GnssAntennaInfo>() { - @Override - public GnssAntennaInfo createFromParcel(Parcel in) { - double carrierFrequencyMHz = in.readDouble(); - - ClassLoader classLoader = getClass().getClassLoader(); - PhaseCenterOffset phaseCenterOffset = - in.readParcelable(classLoader); - SphericalCorrections phaseCenterVariationCorrections = - in.readParcelable(classLoader); - SphericalCorrections signalGainCorrections = - in.readParcelable(classLoader); - - return new GnssAntennaInfo( - carrierFrequencyMHz, - phaseCenterOffset, - phaseCenterVariationCorrections, - signalGainCorrections); - } + public static final @NonNull Creator<GnssAntennaInfo> CREATOR = new Creator<GnssAntennaInfo>() { + @Override + public GnssAntennaInfo createFromParcel(Parcel in) { + double carrierFrequencyMHz = in.readDouble(); + PhaseCenterOffset phaseCenterOffset = + in.readTypedObject(PhaseCenterOffset.CREATOR); + SphericalCorrections phaseCenterVariationCorrections = + in.readTypedObject(SphericalCorrections.CREATOR); + SphericalCorrections signalGainCorrections = + in.readTypedObject(SphericalCorrections.CREATOR); + + return new GnssAntennaInfo( + carrierFrequencyMHz, + phaseCenterOffset, + phaseCenterVariationCorrections, + signalGainCorrections); + } - @Override - public GnssAntennaInfo[] newArray(int size) { - return new GnssAntennaInfo[size]; - } - }; + @Override + public GnssAntennaInfo[] newArray(int size) { + return new GnssAntennaInfo[size]; + } + }; @Override public int describeContents() { @@ -497,9 +550,9 @@ public final class GnssAntennaInfo implements Parcelable { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { parcel.writeDouble(mCarrierFrequencyMHz); - parcel.writeParcelable(mPhaseCenterOffset, flags); - parcel.writeParcelable(mPhaseCenterVariationCorrections, flags); - parcel.writeParcelable(mSignalGainCorrections, flags); + parcel.writeTypedObject(mPhaseCenterOffset, flags); + parcel.writeTypedObject(mPhaseCenterVariationCorrections, flags); + parcel.writeTypedObject(mSignalGainCorrections, flags); } @Override @@ -511,4 +564,26 @@ public final class GnssAntennaInfo implements Parcelable { + ", SignalGainCorrections=" + mSignalGainCorrections + '}'; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof GnssAntennaInfo)) { + return false; + } + GnssAntennaInfo that = (GnssAntennaInfo) o; + return Double.compare(that.mCarrierFrequencyMHz, mCarrierFrequencyMHz) == 0 + && mPhaseCenterOffset.equals(that.mPhaseCenterOffset) + && Objects.equals(mPhaseCenterVariationCorrections, + that.mPhaseCenterVariationCorrections) + && Objects.equals(mSignalGainCorrections, that.mSignalGainCorrections); + } + + @Override + public int hashCode() { + return Objects.hash(mCarrierFrequencyMHz, mPhaseCenterOffset, + mPhaseCenterVariationCorrections, mSignalGainCorrections); + } } diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java index 98ef2d4a6c9f..a07a64acb6e6 100644 --- a/location/java/android/location/GnssMeasurementsEvent.java +++ b/location/java/android/location/GnssMeasurementsEvent.java @@ -16,9 +16,9 @@ package android.location; -import android.annotation.TestApi; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -46,8 +46,10 @@ public final class GnssMeasurementsEvent implements Parcelable { public static abstract class Callback { /** * The status of the GNSS measurements event. + * @deprecated Do not use. * @hide */ + @Deprecated @Retention(RetentionPolicy.SOURCE) @IntDef({STATUS_NOT_SUPPORTED, STATUS_READY, STATUS_LOCATION_DISABLED, STATUS_NOT_ALLOWED}) public @interface GnssMeasurementsStatus {} @@ -56,19 +58,28 @@ public final class GnssMeasurementsEvent implements Parcelable { * The system does not support tracking of GNSS Measurements. * * <p>This status will not change in the future. + * + * @deprecated Do not use. */ + @Deprecated public static final int STATUS_NOT_SUPPORTED = 0; /** * GNSS Measurements are successfully being tracked, it will receive updates once they are * available. + * + * @deprecated Do not use. */ + @Deprecated public static final int STATUS_READY = 1; /** * GPS provider or Location is disabled, updates will not be received until they are * enabled. + * + * @deprecated Do not use. */ + @Deprecated public static final int STATUS_LOCATION_DISABLED = 2; /** @@ -81,7 +92,10 @@ public final class GnssMeasurementsEvent implements Parcelable { * * <p>If such a status is received, one would try again at a later time point where no * other client is having a conflicting request. + * + * @deprecated Do not use. */ + @Deprecated public static final int STATUS_NOT_ALLOWED = 3; /** @@ -91,7 +105,13 @@ public final class GnssMeasurementsEvent implements Parcelable { /** * Reports the latest status of the GNSS Measurements sub-system. + * + * @deprecated Do not rely on this callback. From Android S onwards this callback will be + * invoked once with {@link #STATUS_READY} in all cases for backwards compatibility, and + * then never invoked again. Use LocationManager APIs if you need to determine if + * GNSS measurements are supported or if location is off, etc... */ + @Deprecated public void onStatusChanged(@GnssMeasurementsStatus int status) {} } diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java index aade5ac63317..84a363d25c63 100644 --- a/location/java/android/location/GnssNavigationMessage.java +++ b/location/java/android/location/GnssNavigationMessage.java @@ -110,6 +110,7 @@ public final class GnssNavigationMessage implements Parcelable { public static abstract class Callback { /** * The status of GNSS Navigation Message event. + * @deprecated Do not use. * @hide */ @Retention(RetentionPolicy.SOURCE) @@ -120,19 +121,28 @@ public final class GnssNavigationMessage implements Parcelable { * The system does not support tracking of GNSS Navigation Messages. * * This status will not change in the future. + * + * @deprecated Do not use. */ + @Deprecated public static final int STATUS_NOT_SUPPORTED = 0; /** * GNSS Navigation Messages are successfully being tracked, it will receive updates once * they are available. + * + * @deprecated Do not use. */ + @Deprecated public static final int STATUS_READY = 1; /** * GNSS provider or Location is disabled, updated will not be received until they are * enabled. + * + * @deprecated Do not use. */ + @Deprecated public static final int STATUS_LOCATION_DISABLED = 2; /** @@ -142,7 +152,13 @@ public final class GnssNavigationMessage implements Parcelable { /** * Returns the latest status of the GNSS Navigation Messages sub-system. + * + * @deprecated Do not rely on this callback. From Android S onwards this callback will be + * invoked once with {@link #STATUS_READY} in all cases for backwards compatibility, and + * then never invoked again. Use LocationManager APIs if you need to determine if + * GNSS navigation messages are supported or if location is off, etc... */ + @Deprecated public void onStatusChanged(@GnssNavigationMessageStatus int status) {} } diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java index c5b4c1667831..b46e8ce2f605 100644 --- a/location/java/android/location/GnssStatus.java +++ b/location/java/android/location/GnssStatus.java @@ -391,20 +391,10 @@ public final class GnssStatus implements Parcelable { float[] basebandCn0DbHzs = new float[svCount]; for (int i = 0; i < svCount; i++) { svidWithFlags[i] = in.readInt(); - } - for (int i = 0; i < svCount; i++) { cn0DbHzs[i] = in.readFloat(); - } - for (int i = 0; i < svCount; i++) { elevations[i] = in.readFloat(); - } - for (int i = 0; i < svCount; i++) { azimuths[i] = in.readFloat(); - } - for (int i = 0; i < svCount; i++) { carrierFrequencies[i] = in.readFloat(); - } - for (int i = 0; i < svCount; i++) { basebandCn0DbHzs[i] = in.readFloat(); } @@ -428,20 +418,10 @@ public final class GnssStatus implements Parcelable { parcel.writeInt(mSvCount); for (int i = 0; i < mSvCount; i++) { parcel.writeInt(mSvidWithFlags[i]); - } - for (int i = 0; i < mSvCount; i++) { parcel.writeFloat(mCn0DbHzs[i]); - } - for (int i = 0; i < mSvCount; i++) { parcel.writeFloat(mElevations[i]); - } - for (int i = 0; i < mSvCount; i++) { parcel.writeFloat(mAzimuths[i]); - } - for (int i = 0; i < mSvCount; i++) { parcel.writeFloat(mCarrierFrequencies[i]); - } - for (int i = 0; i < mSvCount; i++) { parcel.writeFloat(mBasebandCn0DbHzs[i]); } } diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 621fe1ba1432..afae50203cd3 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -21,11 +21,11 @@ import android.location.Address; import android.location.Criteria; import android.location.GeocoderParams; import android.location.Geofence; +import android.location.GnssAntennaInfo; import android.location.GnssCapabilities; import android.location.GnssMeasurementCorrections; import android.location.GnssMeasurementRequest; import android.location.IGeocodeListener; -import android.location.IGnssAntennaInfoListener; import android.location.IGnssMeasurementsListener; import android.location.IGnssStatusListener; import android.location.IGnssNavigationMessageListener; @@ -36,7 +36,7 @@ import android.location.LastLocationRequest; import android.location.Location; import android.location.LocationRequest; import android.location.LocationTime; -import android.location.ProviderProperties; +import android.location.provider.ProviderProperties; import android.os.Bundle; import android.os.ICancellationSignal; @@ -76,6 +76,8 @@ interface ILocationManager int getGnssYearOfHardware(); String getGnssHardwareModelName(); + @nullable List<GnssAntennaInfo> getGnssAntennaInfos(); + void registerGnssStatusCallback(in IGnssStatusListener callback, String packageName, String attributionTag); void unregisterGnssStatusCallback(in IGnssStatusListener callback); @@ -86,9 +88,6 @@ interface ILocationManager void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener); void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections); - void addGnssAntennaInfoListener(in IGnssAntennaInfoListener listener, String packageName, String attributionTag); - void removeGnssAntennaInfoListener(in IGnssAntennaInfoListener listener); - void addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener, String packageName, String attributionTag); void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 00381a68a2a2..2dc9eb44236f 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -44,8 +44,12 @@ import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.location.provider.ProviderProperties; import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; @@ -64,6 +68,7 @@ import com.android.internal.listeners.ListenerTransportMultiplexer; import com.android.internal.util.Preconditions; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -312,6 +317,48 @@ public class LocationManager { "android.location.HIGH_POWER_REQUEST_CHANGE"; /** + * Broadcast intent action when GNSS capabilities change. This is most common at boot time as + * GNSS capabilities are queried from the chipset. Includes an intent extra, + * {@link #EXTRA_GNSS_CAPABILITIES}, with the new {@link GnssCapabilities}. + * + * @see #EXTRA_GNSS_CAPABILITIES + * @see #getGnssCapabilities() + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_GNSS_CAPABILITIES_CHANGED = + "android.location.action.GNSS_CAPABILITIES_CHANGED"; + + /** + * Intent extra included with {@link #ACTION_GNSS_CAPABILITIES_CHANGED} broadcasts, containing + * the new {@link GnssCapabilities}. + * + * @see #ACTION_GNSS_CAPABILITIES_CHANGED + */ + public static final String EXTRA_GNSS_CAPABILITIES = "android.location.extra.GNSS_CAPABILITIES"; + + /** + * Broadcast intent action when GNSS antenna infos change. Includes an intent extra, + * {@link #EXTRA_GNSS_ANTENNA_INFOS}, with an ArrayList of the new {@link GnssAntennaInfo}. This + * may be read via {@link android.content.Intent#getParcelableArrayListExtra(String)}. + * + * @see #EXTRA_GNSS_ANTENNA_INFOS + * @see #getGnssAntennaInfos() + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_GNSS_ANTENNA_INFOS_CHANGED = + "android.location.action.GNSS_ANTENNA_INFOS_CHANGED"; + + /** + * Intent extra included with {@link #ACTION_GNSS_ANTENNA_INFOS_CHANGED} broadcasts, containing + * the new ArrayList of {@link GnssAntennaInfo}. This may be read via + * {@link android.content.Intent#getParcelableArrayListExtra(String)}. + * + * @see #ACTION_GNSS_ANTENNA_INFOS_CHANGED + */ + public static final String EXTRA_GNSS_ANTENNA_INFOS = + "android.location.extra.GNSS_ANTENNA_INFOS"; + + /** * Broadcast intent action for Settings app to inject a footer at the bottom of location * settings. This is for use only by apps that are included in the system image. * @@ -1757,7 +1804,6 @@ public class LocationManager { } try { - ProviderProperties properties = mService.getProviderProperties(provider); return new LocationProvider(provider, properties); } catch (IllegalArgumentException e) { @@ -1885,11 +1931,35 @@ public class LocationManager { boolean supportsSpeed, boolean supportsBearing, @ProviderProperties.PowerUsage int powerUsage, @ProviderProperties.Accuracy int accuracy) { + addTestProvider(provider, new ProviderProperties.Builder() + .setHasNetworkRequirement(requiresNetwork) + .setHasSatelliteRequirement(requiresSatellite) + .setHasCellRequirement(requiresCell) + .setHasMonetaryCost(hasMonetaryCost) + .setHasAltitudeSupport(supportsAltitude) + .setHasSpeedSupport(supportsSpeed) + .setHasBearingSupport(supportsBearing) + .setPowerUsage(powerUsage) + .setAccuracy(accuracy) + .build()); + } + + /** + * Creates a test location provider and adds it to the set of active providers. This provider + * will replace any provider with the same name that exists prior to this call. + * + * @param provider the provider name + * + * @throws IllegalArgumentException if provider is null + * @throws IllegalArgumentException if properties is null + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + */ + public void addTestProvider(@NonNull String provider, @NonNull ProviderProperties properties) { Preconditions.checkArgument(provider != null, "invalid null provider"); + Preconditions.checkArgument(properties != null, "invalid null properties"); - ProviderProperties properties = new ProviderProperties(requiresNetwork, - requiresSatellite, requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, - supportsBearing, powerUsage, accuracy); try { mService.addTestProvider(provider, properties, mContext.getOpPackageName(), mContext.getFeatureId()); @@ -2137,6 +2207,20 @@ public class LocationManager { } /** + * Returns the current list of GNSS antenna infos, or null if unknown or unsupported. + * + * @see #getGnssCapabilities() + */ + @Nullable + public List<GnssAntennaInfo> getGnssAntennaInfos() { + try { + return mService.getGnssAntennaInfos(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Retrieves information about the current status of the GPS engine. This should only be called * from within the {@link GpsStatus.Listener#onGpsStatusChanged} callback to ensure that the * data is copied atomically. @@ -2559,15 +2643,19 @@ public class LocationManager { * * <p>Not all GNSS chipsets support antenna info updates, see {@link #getGnssCapabilities()}. * + * <p>Prior to Android S, this requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} + * permission. + * * @param executor the executor that the listener runs on * @param listener the listener to register * @return {@code true} always * * @throws IllegalArgumentException if executor is null * @throws IllegalArgumentException if listener is null - * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present + * + * @deprecated Prefer to use a receiver for {@link #ACTION_GNSS_ANTENNA_INFOS_CHANGED}. */ - @RequiresPermission(ACCESS_FINE_LOCATION) + @Deprecated public boolean registerAntennaInfoListener( @NonNull @CallbackExecutor Executor executor, @NonNull GnssAntennaInfo.Listener listener) { @@ -2579,7 +2667,10 @@ public class LocationManager { * Unregisters a GNSS Antenna Info listener. * * @param listener a {@link GnssAntennaInfo.Listener} object to remove + * + * @deprecated Prefer to use a receiver for {@link #ACTION_GNSS_ANTENNA_INFOS_CHANGED}. */ + @Deprecated public void unregisterAntennaInfoListener(@NonNull GnssAntennaInfo.Listener listener) { getGnssAntennaInfoTransportMultiplexer().removeListener(listener); } @@ -3153,40 +3244,41 @@ public class LocationManager { private class GnssAntennaInfoTransportMultiplexer extends ListenerTransportMultiplexer<Void, GnssAntennaInfo.Listener> { - private @Nullable IGnssAntennaInfoListener mListenerTransport; + private @Nullable BroadcastReceiver mListenerTransport; GnssAntennaInfoTransportMultiplexer() {} @Override - protected void registerWithServer(Void ignored) throws RemoteException { - IGnssAntennaInfoListener transport = mListenerTransport; - if (transport == null) { - transport = new GnssAntennaInfoListener(); + protected void registerWithServer(Void ignored) { + if (mListenerTransport == null) { + // if an exception is thrown the transport should not be set + BroadcastReceiver transport = new GnssAntennaInfoReceiver(); + mContext.registerReceiver(transport, + new IntentFilter(ACTION_GNSS_ANTENNA_INFOS_CHANGED)); + mListenerTransport = transport; } - - // if a remote exception is thrown the transport should not be set - mListenerTransport = null; - mService.addGnssAntennaInfoListener(transport, mContext.getPackageName(), - mContext.getAttributionTag()); - mListenerTransport = transport; } @Override - protected void unregisterWithServer() throws RemoteException { + protected void unregisterWithServer() { if (mListenerTransport != null) { - IGnssAntennaInfoListener transport = mListenerTransport; + BroadcastReceiver transport = mListenerTransport; mListenerTransport = null; - mService.removeGnssAntennaInfoListener(transport); + mContext.unregisterReceiver(transport); } } - private class GnssAntennaInfoListener extends IGnssAntennaInfoListener.Stub { + private class GnssAntennaInfoReceiver extends BroadcastReceiver { - GnssAntennaInfoListener() {} + GnssAntennaInfoReceiver() {} @Override - public void onGnssAntennaInfoReceived(List<GnssAntennaInfo> infos) { - deliverToListeners(callback -> callback.onGnssAntennaInfoReceived(infos)); + public void onReceive(Context context, Intent intent) { + ArrayList<GnssAntennaInfo> infos = intent.getParcelableArrayListExtra( + EXTRA_GNSS_ANTENNA_INFOS); + if (infos != null) { + deliverToListeners(callback -> callback.onGnssAntennaInfoReceived(infos)); + } } } } diff --git a/location/java/android/location/LocationProvider.java b/location/java/android/location/LocationProvider.java index 6d2bfed99fb7..60b251ea990d 100644 --- a/location/java/android/location/LocationProvider.java +++ b/location/java/android/location/LocationProvider.java @@ -17,6 +17,7 @@ package android.location; import android.annotation.Nullable; +import android.location.provider.ProviderProperties; /** * Information about the properties of a location provider. @@ -200,10 +201,8 @@ public class LocationProvider { } /** - * Returns the power requirement for this provider. - * - * @return the power requirement for this provider, as one of the - * constants ProviderProperties.POWER_USAGE_*. + * Returns the power requirement for this provider, one of the ProviderProperties.POWER_USAGE_* + * constants. */ public int getPowerRequirement() { if (mProperties == null) { @@ -214,11 +213,8 @@ public class LocationProvider { } /** - * Returns a constant describing horizontal accuracy of this provider. - * If the provider returns finer grain or exact location, - * {@link ProviderProperties#ACCURACY_FINE} is returned, otherwise if the - * location is only approximate then {@link ProviderProperties#ACCURACY_COARSE} - * is returned. + * Returns the rough accuracy of this provider, one of the ProviderProperties.ACCURACY_* + * constants. */ public int getAccuracy() { if (mProperties == null) { diff --git a/location/java/com/android/internal/location/ILocationProvider.aidl b/location/java/android/location/provider/ILocationProvider.aidl index dac08ff77938..f9995d5cf8b8 100644 --- a/location/java/com/android/internal/location/ILocationProvider.aidl +++ b/location/java/android/location/provider/ILocationProvider.aidl @@ -14,13 +14,12 @@ * limitations under the License. */ -package com.android.internal.location; +package android.location.provider; import android.os.Bundle; -import android.os.WorkSource; -import com.android.internal.location.ILocationProviderManager; -import com.android.internal.location.ProviderRequest; +import android.location.provider.ILocationProviderManager; +import android.location.provider.ProviderRequest; /** * Binder interface for services that implement location providers. Do not implement this directly, @@ -29,14 +28,8 @@ import com.android.internal.location.ProviderRequest; */ interface ILocationProvider { - @UnsupportedAppUsage(maxTargetSdk = 30, publicAlternatives = "{@code Do not use}") oneway void setLocationProviderManager(in ILocationProviderManager manager); - - @UnsupportedAppUsage(maxTargetSdk = 30, publicAlternatives = "{@code Do not use}") - oneway void setRequest(in ProviderRequest request, in WorkSource ws); - + oneway void setRequest(in ProviderRequest request); oneway void flush(); - - @UnsupportedAppUsage(maxTargetSdk = 30, publicAlternatives = "{@code Do not use}") oneway void sendExtraCommand(String command, in Bundle extras); } diff --git a/location/java/com/android/internal/location/ILocationProviderManager.aidl b/location/java/android/location/provider/ILocationProviderManager.aidl index a5b22b2c07d3..e3f51d9a23e1 100644 --- a/location/java/com/android/internal/location/ILocationProviderManager.aidl +++ b/location/java/android/location/provider/ILocationProviderManager.aidl @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.android.internal.location; +package android.location.provider; import android.location.LocationResult; -import android.location.ProviderProperties; +import android.location.provider.ProviderProperties; /** * Binder interface for manager of all location providers. diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java new file mode 100644 index 000000000000..1306ea2c0204 --- /dev/null +++ b/location/java/android/location/provider/LocationProviderBase.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location.provider; + +import static android.location.Location.EXTRA_NO_GPS_LOCATION; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.content.Context; +import android.content.Intent; +import android.location.Location; +import android.location.LocationResult; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Objects; + +/** + * Base class for location providers outside the system server. + * + * Location providers should be wrapped in a non-exported service which returns the result of + * {@link #getBinder()} from the service's {@link android.app.Service#onBind(Intent)} method. The + * service should not be exported so that components other than the system server cannot bind to it. + * Alternatively, the service may be guarded by a permission that only system server can obtain. The + * service may specify metadata on its capabilities: + * + * <ul> + * <li> + * "serviceVersion": An integer version code to help tie break if multiple services are + * capable of implementing the same location provider. All else equal, the service with the + * highest version code will be chosen. Assumed to be 0 if not specified. + * </li> + * <li> + * "serviceIsMultiuser": A boolean property, indicating if the service wishes to take + * responsibility for handling changes to the current user on the device. If true, the + * service will always be bound from the system user. If false, the service will always be + * bound from the current user. If the current user changes, the old binding will be + * released, and a new binding established under the new user. Assumed to be false if not + * specified. + * </li> + * </ul> + * + * <p>The service should have an intent filter in place for the location provider it wishes to + * implements. Defaults for some providers are specified as constants in this class. + * + * @hide + */ +@SystemApi +public abstract class LocationProviderBase { + + /** + * Callback to be invoked when a flush operation is complete and all flushed locations have been + * reported. + */ + public interface OnFlushCompleteCallback { + + /** + * Should be invoked once the flush is complete. + */ + void onFlushComplete(); + } + + /** + * The action the wrapping service should have in its intent filter to implement the + * {@link android.location.LocationManager#NETWORK_PROVIDER}. + */ + @SuppressLint("ActionValue") + public static final String ACTION_NETWORK_PROVIDER = + "com.android.location.service.v3.NetworkLocationProvider"; + + /** + * The action the wrapping service should have in its intent filter to implement the + * {@link android.location.LocationManager#FUSED_PROVIDER}. + */ + @SuppressLint("ActionValue") + public static final String ACTION_FUSED_PROVIDER = + "com.android.location.service.FusedLocationProvider"; + + private final String mTag; + private final @Nullable String mPackageName; + private final @Nullable String mAttributionTag; + private final IBinder mBinder; + + // write locked on mBinder, read lock is optional depending on atomicity requirements + private @Nullable volatile ILocationProviderManager mManager; + private volatile ProviderProperties mProperties; + private volatile boolean mAllowed; + + public LocationProviderBase(@NonNull Context context, @NonNull String tag, + @NonNull ProviderProperties properties) { + mTag = tag; + mPackageName = context.getPackageName(); + mAttributionTag = context.getAttributionTag(); + mBinder = new Service(); + + mManager = null; + mProperties = Objects.requireNonNull(properties); + mAllowed = true; + } + + /** + * Returns the IBinder instance that should be returned from the + * {@link android.app.Service#onBind(Intent)} method of the wrapping service. + */ + public final @Nullable IBinder getBinder() { + return mBinder; + } + + /** + * Sets whether this provider is currently allowed or not. Note that this is specific to the + * provider only, and is unrelated to global location settings. This is a hint to the location + * manager that this provider will be unable to fulfill incoming requests. Setting a provider + * as not allowed will result in the provider being disabled. Setting a provider as allowed + * means that the provider may be in either the enabled or disabled state. + * + * <p>Some guidelines: providers should set their own allowed/disallowed status based only on + * state "owned" by that provider. For instance, providers should not take into account the + * state of the location master setting when setting themselves allowed or disallowed, as this + * state is not owned by a particular provider. If a provider requires some additional user + * consent that is particular to the provider, this should be use to set the allowed/disallowed + * state. If the provider proxies to another provider, the child provider's allowed/disallowed + * state should be taken into account in the parent's allowed state. For most providers, it is + * expected that they will be always allowed. + */ + public void setAllowed(boolean allowed) { + synchronized (mBinder) { + if (mAllowed == allowed) { + return; + } + + mAllowed = allowed; + } + + ILocationProviderManager manager = mManager; + if (manager != null) { + try { + manager.onSetAllowed(mAllowed); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (RuntimeException e) { + Log.w(mTag, e); + } + } + } + + /** + * Returns true if this provider is allowed. Providers start as allowed on construction. + */ + public boolean isAllowed() { + return mAllowed; + } + + /** + * Sets the provider properties that may be queried by clients. Generally speaking, providers + * should try to avoid changing their properties after construction. + */ + public void setProperties(@NonNull ProviderProperties properties) { + synchronized (mBinder) { + mProperties = Objects.requireNonNull(properties); + } + + ILocationProviderManager manager = mManager; + if (manager != null) { + try { + manager.onSetProperties(mProperties); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (RuntimeException e) { + Log.w(mTag, e); + } + } + } + + /** + * Returns the currently set properties of the provider. + */ + public @NonNull ProviderProperties getProperties() { + return mProperties; + } + + /** + * Reports a new location from this provider. + */ + public void reportLocation(@NonNull Location location) { + reportLocation(LocationResult.create(location)); + } + + /** + * Reports a new location result from this provider. + * + * <p>May only be used from Android S onwards. + */ + public void reportLocation(@NonNull LocationResult locationResult) { + ILocationProviderManager manager = mManager; + if (manager != null) { + locationResult = locationResult.map(location -> { + // remove deprecated extras to save on serialization costs + Bundle extras = location.getExtras(); + if (extras != null && (extras.containsKey(EXTRA_NO_GPS_LOCATION) + || extras.containsKey("coarseLocation"))) { + location = new Location(location); + extras = location.getExtras(); + extras.remove(EXTRA_NO_GPS_LOCATION); + extras.remove("coarseLocation"); + if (extras.isEmpty()) { + location.setExtras(null); + } + } + return location; + }); + + try { + manager.onReportLocation(locationResult); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (RuntimeException e) { + Log.w(mTag, e); + } + } + } + + /** + * Set the current {@link ProviderRequest} for this provider. Each call to this method overrides + * any prior ProviderRequests. The provider should immediately attempt to provide locations (or + * not provide locations) according to the parameters of the provider request. + */ + public abstract void onSetRequest(@NonNull ProviderRequest request); + + /** + * Requests a flush of any pending batched locations. The callback must always be invoked once + * per invocation, and should be invoked after {@link #reportLocation(LocationResult)} has been + * invoked with any flushed locations. The callback may be invoked immediately if no locations + * are flushed. + */ + public abstract void onFlush(@NonNull OnFlushCompleteCallback callback); + + /** + * Implements optional custom commands. + */ + public abstract void onSendExtraCommand(@NonNull String command, @Nullable Bundle extras); + + private final class Service extends ILocationProvider.Stub { + + Service() {} + + @Override + public void setLocationProviderManager(ILocationProviderManager manager) { + synchronized (mBinder) { + try { + manager.onInitialize(mAllowed, mProperties, mPackageName, mAttributionTag); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (RuntimeException e) { + Log.w(mTag, e); + } + + mManager = manager; + } + } + + @Override + public void setRequest(ProviderRequest request) { + onSetRequest(request); + } + + @Override + public void flush() { + onFlush(this::onFlushComplete); + } + + private void onFlushComplete() { + ILocationProviderManager manager = mManager; + if (manager != null) { + try { + manager.onFlushComplete(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (RuntimeException e) { + Log.w(mTag, e); + } + } + } + + @Override + public void sendExtraCommand(String command, Bundle extras) { + onSendExtraCommand(command, extras); + } + } +} diff --git a/location/java/android/location/ProviderProperties.aidl b/location/java/android/location/provider/ProviderProperties.aidl index 8b1d79f90233..fd5a614b4fc8 100644 --- a/location/java/android/location/ProviderProperties.aidl +++ b/location/java/android/location/provider/ProviderProperties.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.location; +package android.location.provider; parcelable ProviderProperties; diff --git a/location/java/android/location/ProviderProperties.java b/location/java/android/location/provider/ProviderProperties.java index 8fa8c984b172..793401251de8 100644 --- a/location/java/android/location/ProviderProperties.java +++ b/location/java/android/location/provider/ProviderProperties.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 android.location; +package android.location.provider; import android.annotation.IntDef; import android.annotation.NonNull; @@ -78,10 +78,7 @@ public final class ProviderProperties implements Parcelable { private final @PowerUsage int mPowerUsage; private final @Accuracy int mAccuracy; - /** - * @hide - */ - public ProviderProperties(boolean hasNetworkRequirement, boolean hasSatelliteRequirement, + private ProviderProperties(boolean hasNetworkRequirement, boolean hasSatelliteRequirement, boolean hasCellRequirement, boolean hasMonetaryCost, boolean hasAltitudeSupport, boolean hasSpeedSupport, boolean hasBearingSupport, @PowerUsage int powerUsage, @Accuracy int accuracy) { @@ -92,10 +89,8 @@ public final class ProviderProperties implements Parcelable { mHasAltitudeSupport = hasAltitudeSupport; mHasSpeedSupport = hasSpeedSupport; mHasBearingSupport = hasBearingSupport; - mPowerUsage = Preconditions.checkArgumentInRange(powerUsage, POWER_USAGE_LOW, - POWER_USAGE_HIGH, "powerUsage"); - mAccuracy = Preconditions.checkArgumentInRange(accuracy, ACCURACY_FINE, - ACCURACY_COARSE, "locationAccuracy"); + mPowerUsage = powerUsage; + mAccuracy = accuracy; } /** @@ -233,7 +228,7 @@ public final class ProviderProperties implements Parcelable { @Override public String toString() { StringBuilder b = new StringBuilder("ProviderProperties["); - b.append("power=").append(powerToString(mPowerUsage)).append(", "); + b.append("powerUsage=").append(powerToString(mPowerUsage)).append(", "); b.append("accuracy=").append(accuracyToString(mAccuracy)); if (mHasNetworkRequirement || mHasSatelliteRequirement || mHasCellRequirement) { b.append(", requires="); @@ -292,4 +287,128 @@ public final class ProviderProperties implements Parcelable { throw new AssertionError(); } } + + /** + * Builder for ProviderProperties. + */ + public static final class Builder { + + private boolean mHasNetworkRequirement; + private boolean mHasSatelliteRequirement; + private boolean mHasCellRequirement; + private boolean mHasMonetaryCost; + private boolean mHasAltitudeSupport; + private boolean mHasSpeedSupport; + private boolean mHasBearingSupport; + private @PowerUsage int mPowerUsage; + private @Accuracy int mAccuracy; + + public Builder() { + mHasNetworkRequirement = false; + mHasSatelliteRequirement = false; + mHasCellRequirement = false; + mHasMonetaryCost = false; + mHasAltitudeSupport = false; + mHasSpeedSupport = false; + mHasBearingSupport = false; + mPowerUsage = POWER_USAGE_HIGH; + mAccuracy = ACCURACY_COARSE; + } + + public Builder(@NonNull ProviderProperties providerProperties) { + mHasNetworkRequirement = providerProperties.mHasNetworkRequirement; + mHasSatelliteRequirement = providerProperties.mHasSatelliteRequirement; + mHasCellRequirement = providerProperties.mHasCellRequirement; + mHasMonetaryCost = providerProperties.mHasMonetaryCost; + mHasAltitudeSupport = providerProperties.mHasAltitudeSupport; + mHasSpeedSupport = providerProperties.mHasSpeedSupport; + mHasBearingSupport = providerProperties.mHasBearingSupport; + mPowerUsage = providerProperties.mPowerUsage; + mAccuracy = providerProperties.mAccuracy; + } + + /** + * Sets whether a provider requires network access. False by default. + */ + public @NonNull Builder setHasNetworkRequirement(boolean requiresNetwork) { + mHasNetworkRequirement = requiresNetwork; + return this; + } + + /** + * Sets whether a provider requires satellite access. False by default. + */ + public @NonNull Builder setHasSatelliteRequirement(boolean requiresSatellite) { + mHasSatelliteRequirement = requiresSatellite; + return this; + } + + /** + * Sets whether a provider requires cell tower access. False by default. + */ + public @NonNull Builder setHasCellRequirement(boolean requiresCell) { + mHasCellRequirement = requiresCell; + return this; + } + + /** + * Sets whether a provider has a monetary cost. False by default. + */ + public @NonNull Builder setHasMonetaryCost(boolean monetaryCost) { + mHasMonetaryCost = monetaryCost; + return this; + } + + /** + * Sets whether a provider can provide altitude information. False by default. + */ + public @NonNull Builder setHasAltitudeSupport(boolean supportsAltitude) { + mHasAltitudeSupport = supportsAltitude; + return this; + } + + /** + * Sets whether a provider can provide speed information. False by default. + */ + public @NonNull Builder setHasSpeedSupport(boolean supportsSpeed) { + mHasSpeedSupport = supportsSpeed; + return this; + } + + /** + * Sets whether a provider can provide bearing information. False by default. + */ + public @NonNull Builder setHasBearingSupport(boolean supportsBearing) { + mHasBearingSupport = supportsBearing; + return this; + } + + /** + * Sets a very rough bucket of provider power usage. {@link #POWER_USAGE_HIGH} by default. + */ + public @NonNull Builder setPowerUsage(@PowerUsage int powerUsage) { + mPowerUsage = Preconditions.checkArgumentInRange(powerUsage, POWER_USAGE_LOW, + POWER_USAGE_HIGH, "powerUsage"); + return this; + } + + /** + * Sets a very rough bucket of provider location accuracy. {@link #ACCURACY_COARSE} by + * default. + */ + public @NonNull Builder setAccuracy(@Accuracy int accuracy) { + mAccuracy = Preconditions.checkArgumentInRange(accuracy, ACCURACY_FINE, + ACCURACY_COARSE, "accuracy"); + return this; + } + + /** + * Builds a new ProviderProperties. + */ + public @NonNull ProviderProperties build() { + return new ProviderProperties(mHasNetworkRequirement, mHasSatelliteRequirement, + mHasCellRequirement, mHasMonetaryCost, mHasAltitudeSupport, mHasSpeedSupport, + mHasBearingSupport, mPowerUsage, mAccuracy); + } + } } diff --git a/location/java/com/android/internal/location/ProviderRequest.aidl b/location/java/android/location/provider/ProviderRequest.aidl index 4e1ea955c2e5..b98f3018fe2b 100644 --- a/location/java/com/android/internal/location/ProviderRequest.aidl +++ b/location/java/android/location/provider/ProviderRequest.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.internal.location; +package android.location.provider; parcelable ProviderRequest; diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/android/location/provider/ProviderRequest.java index 545ea528826b..e543b040a2d4 100644 --- a/location/java/com/android/internal/location/ProviderRequest.java +++ b/location/java/android/location/provider/ProviderRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.location; +package android.location.provider; import static android.location.LocationRequest.QUALITY_BALANCED_POWER_ACCURACY; import static android.location.LocationRequest.QUALITY_HIGH_ACCURACY; @@ -22,10 +22,9 @@ import static android.location.LocationRequest.QUALITY_LOW_POWER; import android.annotation.IntRange; import android.annotation.NonNull; -import android.compat.annotation.UnsupportedAppUsage; +import android.annotation.SystemApi; import android.location.LocationRequest; import android.location.LocationRequest.Quality; -import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.os.WorkSource; @@ -33,34 +32,25 @@ import android.util.TimeUtils; import com.android.internal.util.Preconditions; -import java.util.Collections; -import java.util.List; import java.util.Objects; /** * Location provider request. * @hide */ +@SystemApi public final class ProviderRequest implements Parcelable { public static final long INTERVAL_DISABLED = Long.MAX_VALUE; - public static final ProviderRequest EMPTY_REQUEST = new ProviderRequest( + public static final @NonNull ProviderRequest EMPTY_REQUEST = new ProviderRequest( INTERVAL_DISABLED, QUALITY_BALANCED_POWER_ACCURACY, 0, false, false, new WorkSource()); - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link " - + "ProviderRequest}") - public final boolean reportLocation; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link " - + "ProviderRequest}") - public final long interval; + private final long mIntervalMillis; private final @Quality int mQuality; private final long mMaxUpdateDelayMillis; private final boolean mLowPower; private final boolean mLocationSettingsIgnored; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link " - + "ProviderRequest}") - public final List<LocationRequest> locationRequests; private final WorkSource mWorkSource; private ProviderRequest( @@ -70,22 +60,11 @@ public final class ProviderRequest implements Parcelable { boolean lowPower, boolean locationSettingsIgnored, @NonNull WorkSource workSource) { - reportLocation = intervalMillis != INTERVAL_DISABLED; - interval = intervalMillis; + mIntervalMillis = intervalMillis; mQuality = quality; mMaxUpdateDelayMillis = maxUpdateDelayMillis; mLowPower = lowPower; mLocationSettingsIgnored = locationSettingsIgnored; - if (intervalMillis != INTERVAL_DISABLED) { - locationRequests = Collections.singletonList(new LocationRequest.Builder(intervalMillis) - .setQuality(quality) - .setLowPower(lowPower) - .setLocationSettingsIgnored(locationSettingsIgnored) - .setWorkSource(workSource) - .build()); - } else { - locationRequests = Collections.emptyList(); - } mWorkSource = Objects.requireNonNull(workSource); } @@ -94,7 +73,7 @@ public final class ProviderRequest implements Parcelable { * request is inactive and does not require any locations to be reported. */ public boolean isActive() { - return interval != INTERVAL_DISABLED; + return mIntervalMillis != INTERVAL_DISABLED; } /** @@ -102,7 +81,7 @@ public final class ProviderRequest implements Parcelable { * {@link #INTERVAL_DISABLED} for an inactive request. */ public @IntRange(from = 0) long getIntervalMillis() { - return interval; + return mIntervalMillis; } /** @@ -148,29 +127,28 @@ public final class ProviderRequest implements Parcelable { return mWorkSource; } - public static final Parcelable.Creator<ProviderRequest> CREATOR = - new Parcelable.Creator<ProviderRequest>() { - @Override - public ProviderRequest createFromParcel(Parcel in) { - long intervalMillis = in.readLong(); - if (intervalMillis == INTERVAL_DISABLED) { - return EMPTY_REQUEST; - } else { - return new ProviderRequest( - intervalMillis, - /* quality= */ in.readInt(), - /* maxUpdateDelayMillis= */ in.readLong(), - /* lowPower= */ in.readBoolean(), - /* locationSettingsIgnored= */ in.readBoolean(), - /* workSource= */ in.readTypedObject(WorkSource.CREATOR)); - } - } + public static final @NonNull Creator<ProviderRequest> CREATOR = new Creator<ProviderRequest>() { + @Override + public ProviderRequest createFromParcel(Parcel in) { + long intervalMillis = in.readLong(); + if (intervalMillis == INTERVAL_DISABLED) { + return EMPTY_REQUEST; + } else { + return new ProviderRequest( + intervalMillis, + /* quality= */ in.readInt(), + /* maxUpdateDelayMillis= */ in.readLong(), + /* lowPower= */ in.readBoolean(), + /* locationSettingsIgnored= */ in.readBoolean(), + /* workSource= */ in.readTypedObject(WorkSource.CREATOR)); + } + } - @Override - public ProviderRequest[] newArray(int size) { - return new ProviderRequest[size]; - } - }; + @Override + public ProviderRequest[] newArray(int size) { + return new ProviderRequest[size]; + } + }; @Override public int describeContents() { @@ -178,9 +156,9 @@ public final class ProviderRequest implements Parcelable { } @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeLong(interval); - if (interval != INTERVAL_DISABLED) { + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeLong(mIntervalMillis); + if (mIntervalMillis != INTERVAL_DISABLED) { parcel.writeInt(mQuality); parcel.writeLong(mMaxUpdateDelayMillis); parcel.writeBoolean(mLowPower); @@ -199,10 +177,10 @@ public final class ProviderRequest implements Parcelable { } ProviderRequest that = (ProviderRequest) o; - if (interval == INTERVAL_DISABLED) { - return that.interval == INTERVAL_DISABLED; + if (mIntervalMillis == INTERVAL_DISABLED) { + return that.mIntervalMillis == INTERVAL_DISABLED; } else { - return interval == that.interval + return mIntervalMillis == that.mIntervalMillis && mQuality == that.mQuality && mMaxUpdateDelayMillis == that.mMaxUpdateDelayMillis && mLowPower == that.mLowPower @@ -213,16 +191,16 @@ public final class ProviderRequest implements Parcelable { @Override public int hashCode() { - return Objects.hash(interval, mQuality, mWorkSource); + return Objects.hash(mIntervalMillis, mQuality, mWorkSource); } @Override public String toString() { StringBuilder s = new StringBuilder(); s.append("ProviderRequest["); - if (interval != INTERVAL_DISABLED) { + if (mIntervalMillis != INTERVAL_DISABLED) { s.append("@"); - TimeUtils.formatDuration(interval, s); + TimeUtils.formatDuration(mIntervalMillis, s); if (mQuality != QUALITY_BALANCED_POWER_ACCURACY) { if (mQuality == QUALITY_HIGH_ACCURACY) { s.append(", HIGH_ACCURACY"); @@ -230,7 +208,7 @@ public final class ProviderRequest implements Parcelable { s.append(", LOW_POWER"); } } - if (mMaxUpdateDelayMillis / 2 > interval) { + if (mMaxUpdateDelayMillis / 2 > mIntervalMillis) { s.append(", maxUpdateDelay="); TimeUtils.formatDuration(mMaxUpdateDelayMillis, s); } @@ -253,7 +231,7 @@ public final class ProviderRequest implements Parcelable { /** * A Builder for {@link ProviderRequest}s. */ - public static class Builder { + public static final class Builder { private long mIntervalMillis = INTERVAL_DISABLED; private int mQuality = QUALITY_BALANCED_POWER_ACCURACY; private long mMaxUpdateDelayMillis = 0; diff --git a/location/lib/api/current.txt b/location/lib/api/current.txt index 4e13487759ed..338d7ccd6c91 100644 --- a/location/lib/api/current.txt +++ b/location/lib/api/current.txt @@ -6,33 +6,33 @@ package com.android.location.provider { method @Deprecated public android.os.IBinder getBinder(); } - public abstract class LocationProviderBase { + @Deprecated public abstract class LocationProviderBase { ctor @Deprecated public LocationProviderBase(String, com.android.location.provider.ProviderPropertiesUnbundled); - ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public LocationProviderBase(android.content.Context, String, com.android.location.provider.ProviderPropertiesUnbundled); - method public android.os.IBinder getBinder(); - method @RequiresApi(android.os.Build.VERSION_CODES.R) public boolean isAllowed(); + ctor @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.R) public LocationProviderBase(android.content.Context, String, com.android.location.provider.ProviderPropertiesUnbundled); + method @Deprecated public android.os.IBinder getBinder(); + method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.R) public boolean isAllowed(); method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public boolean isEnabled(); method @Deprecated protected void onDisable(); method @Deprecated protected void onDump(java.io.FileDescriptor, java.io.PrintWriter, String[]); method @Deprecated protected void onEnable(); - method protected void onFlush(com.android.location.provider.LocationProviderBase.OnFlushCompleteCallback); + method @Deprecated protected void onFlush(com.android.location.provider.LocationProviderBase.OnFlushCompleteCallback); method @Deprecated protected int onGetStatus(android.os.Bundle); method @Deprecated protected long onGetStatusUpdateTime(); - method protected void onInit(); - method protected boolean onSendExtraCommand(@Nullable String, @Nullable android.os.Bundle); - method protected abstract void onSetRequest(com.android.location.provider.ProviderRequestUnbundled, android.os.WorkSource); - method public void reportLocation(android.location.Location); - method public void reportLocation(android.location.LocationResult); + method @Deprecated protected void onInit(); + method @Deprecated protected boolean onSendExtraCommand(@Nullable String, @Nullable android.os.Bundle); + method @Deprecated protected abstract void onSetRequest(com.android.location.provider.ProviderRequestUnbundled, android.os.WorkSource); + method @Deprecated public void reportLocation(android.location.Location); + method @Deprecated public void reportLocation(android.location.LocationResult); method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setAdditionalProviderPackages(java.util.List<java.lang.String>); - method @RequiresApi(android.os.Build.VERSION_CODES.R) public void setAllowed(boolean); + method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.R) public void setAllowed(boolean); method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setEnabled(boolean); - method @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setProperties(com.android.location.provider.ProviderPropertiesUnbundled); + method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setProperties(com.android.location.provider.ProviderPropertiesUnbundled); field @Deprecated public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation"; - field public static final String FUSED_PROVIDER = "fused"; + field @Deprecated public static final String FUSED_PROVIDER = "fused"; } - protected static interface LocationProviderBase.OnFlushCompleteCallback { - method public void onFlushComplete(); + @Deprecated protected static interface LocationProviderBase.OnFlushCompleteCallback { + method @Deprecated public void onFlushComplete(); } @Deprecated public final class LocationRequestUnbundled { diff --git a/location/lib/api/system-current.txt b/location/lib/api/system-current.txt index d802177e249b..7046abd8fd3b 100644 --- a/location/lib/api/system-current.txt +++ b/location/lib/api/system-current.txt @@ -1 +1,62 @@ // Signature format: 2.0 +package com.android.location.provider { + + @Deprecated public final class FusedLocationHardware { + method @Deprecated public void flushBatchedLocations(); + method @Deprecated public int getSupportedBatchSize(); + method @Deprecated public int getVersion(); + method @Deprecated public void injectDeviceContext(int); + method @Deprecated public void injectDiagnosticData(String); + method @Deprecated public void registerSink(com.android.location.provider.FusedLocationHardwareSink, android.os.Looper); + method @Deprecated public void requestBatchOfLocations(int); + method @Deprecated public void startBatching(int, com.android.location.provider.GmsFusedBatchOptions); + method @Deprecated public void stopBatching(int); + method @Deprecated public boolean supportsDeviceContextInjection(); + method @Deprecated public boolean supportsDiagnosticDataInjection(); + method @Deprecated public void unregisterSink(com.android.location.provider.FusedLocationHardwareSink); + method @Deprecated public void updateBatchingOptions(int, com.android.location.provider.GmsFusedBatchOptions); + } + + @Deprecated public class FusedLocationHardwareSink { + ctor @Deprecated public FusedLocationHardwareSink(); + method @Deprecated public void onCapabilities(int); + method @Deprecated public void onDiagnosticDataAvailable(String); + method @Deprecated public void onLocationAvailable(android.location.Location[]); + method @Deprecated public void onStatusChanged(int); + } + + @Deprecated public class GmsFusedBatchOptions { + ctor @Deprecated public GmsFusedBatchOptions(); + method @Deprecated public int getFlags(); + method @Deprecated public double getMaxPowerAllocationInMW(); + method @Deprecated public long getPeriodInNS(); + method @Deprecated public float getSmallestDisplacementMeters(); + method @Deprecated public int getSourcesToUse(); + method @Deprecated public boolean isFlagSet(int); + method @Deprecated public boolean isSourceToUseSet(int); + method @Deprecated public void resetFlag(int); + method @Deprecated public void resetSourceToUse(int); + method @Deprecated public void setFlag(int); + method @Deprecated public void setMaxPowerAllocationInMW(double); + method @Deprecated public void setPeriodInNS(long); + method @Deprecated public void setSmallestDisplacementMeters(float); + method @Deprecated public void setSourceToUse(int); + } + + @Deprecated public static final class GmsFusedBatchOptions.BatchFlags { + ctor @Deprecated public GmsFusedBatchOptions.BatchFlags(); + field @Deprecated public static int CALLBACK_ON_LOCATION_FIX; + field @Deprecated public static int WAKEUP_ON_FIFO_FULL; + } + + @Deprecated public static final class GmsFusedBatchOptions.SourceTechnologies { + ctor @Deprecated public GmsFusedBatchOptions.SourceTechnologies(); + field @Deprecated public static int BLUETOOTH; + field @Deprecated public static int CELL; + field @Deprecated public static int GNSS; + field @Deprecated public static int SENSORS; + field @Deprecated public static int WIFI; + } + +} + diff --git a/location/lib/java/com/android/location/provider/FusedLocationHardware.java b/location/lib/java/com/android/location/provider/FusedLocationHardware.java new file mode 100644 index 000000000000..3d323867f026 --- /dev/null +++ b/location/lib/java/com/android/location/provider/FusedLocationHardware.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.location.provider; + +import android.annotation.SystemApi; +import android.os.Looper; + +/** + * Class that exposes IFusedLocationHardware functionality to unbundled services. + * + * @deprecated This class may no longer be used from Android P and onwards. + * @hide + */ +@Deprecated +@SystemApi +public final class FusedLocationHardware { + + private FusedLocationHardware() {} + + /* + * Methods to provide a Facade for IFusedLocationHardware + */ + public void registerSink(FusedLocationHardwareSink sink, Looper looper) {} + + public void unregisterSink(FusedLocationHardwareSink sink) {} + + public int getSupportedBatchSize() { + return 0; + } + + public void startBatching(int id, GmsFusedBatchOptions batchOptions) {} + + public void stopBatching(int id) {} + + public void updateBatchingOptions(int id, GmsFusedBatchOptions batchOptions) {} + + public void requestBatchOfLocations(int batchSizeRequest) {} + + public void flushBatchedLocations() {} + + public boolean supportsDiagnosticDataInjection() { + return false; + } + + public void injectDiagnosticData(String data) {} + + public boolean supportsDeviceContextInjection() { + return false; + } + + public void injectDeviceContext(int deviceEnabledContext) {} + + /** + * Returns the version of the FLP HAL. + * + * <p>Version 1 is the initial release. + * <p>Version 2 adds the ability to use {@link #flushBatchedLocations}, + * {@link FusedLocationHardwareSink#onCapabilities}, and + * {@link FusedLocationHardwareSink#onStatusChanged}. + * + * <p>This method is only available on API 23 or later. Older APIs have version 1. + */ + public int getVersion() { + return 1; + } +} diff --git a/location/lib/java/com/android/location/provider/FusedLocationHardwareSink.java b/location/lib/java/com/android/location/provider/FusedLocationHardwareSink.java new file mode 100644 index 000000000000..30bb1b3a5476 --- /dev/null +++ b/location/lib/java/com/android/location/provider/FusedLocationHardwareSink.java @@ -0,0 +1,73 @@ +/* + * 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.location.provider; + +import android.annotation.SystemApi; +import android.location.Location; + +/** + * Base class for sinks to interact with FusedLocationHardware. + * + * <p>Default implementations allow new methods to be added without crashing + * clients compiled against an old library version. + * + * @deprecated This class may no longer be used from Android P and onwards. + * @hide + */ +@Deprecated +@SystemApi +public class FusedLocationHardwareSink { + /** + * Called when one or more locations are available from the FLP + * HAL. + */ + public void onLocationAvailable(Location[] locations) { + // default do nothing + } + + /** + * Called when diagnostic data is available from the FLP HAL. + */ + public void onDiagnosticDataAvailable(String data) { + // default do nothing + } + + /** + * Called when capabilities are available from the FLP HAL. + * Should be called once right after initialization. + * + * @param capabilities A bitmask of capabilities defined in + * fused_location.h. + */ + public void onCapabilities(int capabilities) { + // default do nothing + } + + /** + * Called when the status changes in the underlying FLP HAL + * implementation (the ability to compute location). This + * callback will only be made on version 2 or later + * (see {@link FusedLocationHardware#getVersion()}). + * + * @param status One of FLP_STATUS_LOCATION_AVAILABLE or + * FLP_STATUS_LOCATION_UNAVAILABLE as defined in + * fused_location.h. + */ + public void onStatusChanged(int status) { + // default do nothing + } +} diff --git a/location/lib/java/com/android/location/provider/GmsFusedBatchOptions.java b/location/lib/java/com/android/location/provider/GmsFusedBatchOptions.java new file mode 100644 index 000000000000..3647377fdcdf --- /dev/null +++ b/location/lib/java/com/android/location/provider/GmsFusedBatchOptions.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.location.provider; + +import android.annotation.SystemApi; + +/** + * Class that exposes FusedBatchOptions to the GmsCore. + * + * @deprecated This class may no longer be used from Android P and onwards. + * @hide + */ +@Deprecated +@SystemApi +public class GmsFusedBatchOptions { + + public void setMaxPowerAllocationInMW(double value) {} + + public double getMaxPowerAllocationInMW() { + return 0; + } + + public void setPeriodInNS(long value) {} + + public long getPeriodInNS() { + return 0; + } + + public void setSmallestDisplacementMeters(float value) {} + + public float getSmallestDisplacementMeters() { + return 0; + } + + public void setSourceToUse(int source) {} + + public void resetSourceToUse(int source) {} + + public boolean isSourceToUseSet(int source) { + return false; + } + + public int getSourcesToUse() { + return 0; + } + + public void setFlag(int flag) {} + + public void resetFlag(int flag) {} + + public boolean isFlagSet(int flag) { + return false; + } + + public int getFlags() { + return 0; + } + + /** + * Definition of enum flag sets needed by this class. + * Such values need to be kept in sync with the ones in fused_location.h + */ + public static final class SourceTechnologies { + public static int GNSS = 1 << 0; + public static int WIFI = 1 << 1; + public static int SENSORS = 1 << 2; + public static int CELL = 1 << 3; + public static int BLUETOOTH = 1 << 4; + } + + public static final class BatchFlags { + public static int WAKEUP_ON_FIFO_FULL = 1 << 0; + public static int CALLBACK_ON_LOCATION_FIX = 1 << 1; + } +} diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java index 47e425629783..b545a834529d 100644 --- a/location/lib/java/com/android/location/provider/LocationProviderBase.java +++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java @@ -23,7 +23,10 @@ import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; import android.location.LocationResult; -import android.location.ProviderProperties; +import android.location.provider.ILocationProvider; +import android.location.provider.ILocationProviderManager; +import android.location.provider.ProviderProperties; +import android.location.provider.ProviderRequest; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.IBinder; @@ -34,10 +37,6 @@ import android.util.Log; import androidx.annotation.RequiresApi; -import com.android.internal.location.ILocationProvider; -import com.android.internal.location.ILocationProviderManager; -import com.android.internal.location.ProviderRequest; - import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; @@ -58,7 +57,11 @@ import java.util.List; * <p>IMPORTANT: This class is effectively a public API for unbundled * applications, and must remain API stable. See README.txt in the root * of this package for more information. + * + * @deprecated This class is not part of the standard API surface - use + * {@link android.location.provider.LocationProviderBase} instead. */ +@Deprecated public abstract class LocationProviderBase { /** @@ -386,8 +389,8 @@ public abstract class LocationProviderBase { } @Override - public void setRequest(ProviderRequest request, WorkSource ws) { - onSetRequest(new ProviderRequestUnbundled(request), ws); + public void setRequest(ProviderRequest request) { + onSetRequest(new ProviderRequestUnbundled(request), request.getWorkSource()); } @Override diff --git a/location/lib/java/com/android/location/provider/ProviderPropertiesUnbundled.java b/location/lib/java/com/android/location/provider/ProviderPropertiesUnbundled.java index 9d8ccdf62ca5..89ca2822a19d 100644 --- a/location/lib/java/com/android/location/provider/ProviderPropertiesUnbundled.java +++ b/location/lib/java/com/android/location/provider/ProviderPropertiesUnbundled.java @@ -17,7 +17,7 @@ package com.android.location.provider; import android.annotation.NonNull; -import android.location.ProviderProperties; +import android.location.provider.ProviderProperties; import java.util.Objects; @@ -35,10 +35,18 @@ public final class ProviderPropertiesUnbundled { public static @NonNull ProviderPropertiesUnbundled create(boolean requiresNetwork, boolean requiresSatellite, boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, boolean supportsSpeed, boolean supportsBearing, - int powerRequirement, int accuracy) { - return new ProviderPropertiesUnbundled(new ProviderProperties(requiresNetwork, - requiresSatellite, requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, - supportsBearing, powerRequirement, accuracy)); + int powerUsage, int accuracy) { + return new ProviderPropertiesUnbundled(new ProviderProperties.Builder() + .setHasNetworkRequirement(requiresNetwork) + .setHasSatelliteRequirement(requiresSatellite) + .setHasCellRequirement(requiresCell) + .setHasMonetaryCost(requiresCell) + .setHasAltitudeSupport(requiresCell) + .setHasSpeedSupport(requiresCell) + .setHasBearingSupport(requiresCell) + .setPowerUsage(powerUsage) + .setAccuracy(accuracy) + .build()); } private final ProviderProperties mProperties; diff --git a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java index b464fca6f596..28317fed9eee 100644 --- a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java +++ b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java @@ -18,13 +18,12 @@ package com.android.location.provider; import android.annotation.NonNull; import android.location.LocationRequest; +import android.location.provider.ProviderRequest; import android.os.Build; import android.os.WorkSource; import androidx.annotation.RequiresApi; -import com.android.internal.location.ProviderRequest; - import java.util.Collections; import java.util.List; diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 8ea380a9f3b7..21f8623b1953 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -5019,13 +5019,18 @@ public class ExifInterface { @Override public int skipBytes(int byteCount) throws IOException { - int totalSkip = Math.min(byteCount, mLength - mPosition); - int skipped = 0; - while (skipped < totalSkip) { - skipped += mDataInputStream.skipBytes(totalSkip - skipped); + int totalBytesToSkip = Math.min(byteCount, mLength - mPosition); + int totalSkipped = 0; + while (totalSkipped < totalBytesToSkip) { + int skipped = mDataInputStream.skipBytes(totalBytesToSkip - totalSkipped); + if (skipped > 0) { + totalSkipped += skipped; + } else { + break; + } } - mPosition += skipped; - return skipped; + mPosition += totalSkipped; + return totalSkipped; } public int readUnsignedShort() throws IOException { diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 36f7bed03bcd..6cf99e288f1b 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -43,8 +43,8 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.Log; +import android.util.SparseIntArray; import android.view.Display; import android.view.DisplayAddress; @@ -55,7 +55,6 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; @@ -99,6 +98,7 @@ public class MediaRouter { RouteInfo mDefaultAudioVideo; RouteInfo mBluetoothA2dpRoute; + boolean mIsBluetoothA2dpOn; RouteInfo mSelectedRoute; @@ -113,11 +113,16 @@ public class MediaRouter { IMediaRouterClient mClient; MediaRouterClientState mClientState; - Map<Integer, Integer> mStreamVolume = new ArrayMap<>(); + SparseIntArray mStreamVolume = new SparseIntArray(); final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() { @Override public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) { + try { + mIsBluetoothA2dpOn = mAudioService.isBluetoothA2dpOn(); + } catch (RemoteException e) { + Log.e(TAG, "Error querying Bluetooth A2DP state", e); + } mHandler.post(new Runnable() { @Override public void run() { updateAudioRoutes(newRoutes); @@ -267,23 +272,23 @@ public class MediaRouter { } int getStreamVolume(int streamType) { - if (!mStreamVolume.containsKey(streamType)) { + int idx = mStreamVolume.indexOfKey(streamType); + if (idx < 0) { + int volume = 0; try { - mStreamVolume.put(streamType, mAudioService.getStreamVolume(streamType)); + volume = mAudioService.getStreamVolume(streamType); + mStreamVolume.put(streamType, volume); } catch (RemoteException e) { Log.e(TAG, "Error getting local stream volume", e); + } finally { + return volume; } } - return mStreamVolume.get(streamType); + return mStreamVolume.valueAt(idx); } boolean isBluetoothA2dpOn() { - try { - return mBluetoothA2dpRoute != null && mAudioService.isBluetoothA2dpOn(); - } catch (RemoteException e) { - Log.e(TAG, "Error querying Bluetooth A2DP state", e); - return false; - } + return mBluetoothA2dpRoute != null && mIsBluetoothA2dpOn; } void updateDiscoveryRequest() { @@ -1444,12 +1449,8 @@ public class MediaRouter { selectedRoute == sStatic.mDefaultAudioVideo) { dispatchRouteVolumeChanged(selectedRoute); } else if (sStatic.mBluetoothA2dpRoute != null) { - try { - dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ? - sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo); - } catch (RemoteException e) { - Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e); - } + dispatchRouteVolumeChanged(sStatic.mIsBluetoothA2dpOn + ? sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo); } else { dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo); } diff --git a/media/java/android/media/musicrecognition/OWNERS b/media/java/android/media/musicrecognition/OWNERS new file mode 100644 index 000000000000..58f5d40dd8c3 --- /dev/null +++ b/media/java/android/media/musicrecognition/OWNERS @@ -0,0 +1,6 @@ +# Bug component: 830636 + +joannechung@google.com +oni@google.com +volnov@google.com + diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index f22bcd88f8c3..1fd132d00f10 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -547,6 +547,7 @@ public final class MediaSessionManager { * @param keyEvent the key event to send * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent) { dispatchMediaKeyEventInternal(keyEvent, /*asSystemService=*/false, /*needWakeLock=*/false); } @@ -558,6 +559,7 @@ public final class MediaSessionManager { * @param needWakeLock true if a wake lock should be held while sending the key * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) { dispatchMediaKeyEventInternal(keyEvent, /*asSystemService=*/false, needWakeLock); } @@ -630,6 +632,7 @@ public final class MediaSessionManager { * @param musicOnly true if key event should only be sent to music stream * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int streamType, boolean musicOnly) { dispatchVolumeKeyEventInternal(keyEvent, streamType, musicOnly, /*asSystemService=*/false); diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java index 67d6e9d266c8..e96cae6ba1bc 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java @@ -46,6 +46,10 @@ public class FrontendInfo { FrontendCapabilities frontendCap) { mId = id; mType = type; + // if max Frequency is negative, we set it as max value of the Integer. + if (maxFrequency < 0) { + maxFrequency = Integer.MAX_VALUE; + } mFrequencyRange = new Range<>(minFrequency, maxFrequency); mSymbolRateRange = new Range<>(minSymbolRate, maxSymbolRate); mAcquireRange = acquireRange; diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 602364e9e01a..a4abf3693096 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -196,8 +196,9 @@ static fields_t gFields; 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 @@ void DestroyCallback(const C2Buffer * /* buf */, void *arg) { event->mAvHandleRefCnt--; event->finalize(); + event->decStrong(buf); } namespace android { @@ -408,6 +410,7 @@ jobject MediaEvent::getLinearBlock() { pC2Buffer->setInfo(info); } pC2Buffer->registerOnDestroyNotify(&DestroyCallback, this); + incStrong(pC2Buffer.get()); jobject linearBlock = env->NewObject( env->FindClass("android/media/MediaCodec$LinearBlock"), @@ -4411,6 +4414,7 @@ static jobject android_media_tv_Tuner_media_event_get_linear_block( ALOGD("Failed get MediaEvent"); return NULL; } + android::Mutex::Autolock autoLock(mediaEventSp->mLock); return mediaEventSp->getLinearBlock(); } diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp index 4aeebe47f1ae..0f6190722aa3 100644 --- a/native/graphics/jni/imagedecoder.cpp +++ b/native/graphics/jni/imagedecoder.cpp @@ -61,6 +61,37 @@ int ResultToErrorCode(SkCodec::Result result) { } } +const char* AImageDecoder_resultToString(int result) { + switch (result) { + case ANDROID_IMAGE_DECODER_SUCCESS: + return "ANDROID_IMAGE_DECODER_SUCCESS"; + case ANDROID_IMAGE_DECODER_INCOMPLETE: + return "ANDROID_IMAGE_DECODER_INCOMPLETE"; + case ANDROID_IMAGE_DECODER_ERROR: + return "ANDROID_IMAGE_DECODER_ERROR"; + case ANDROID_IMAGE_DECODER_INVALID_CONVERSION: + return "ANDROID_IMAGE_DECODER_INVALID_CONVERSION"; + case ANDROID_IMAGE_DECODER_INVALID_SCALE: + return "ANDROID_IMAGE_DECODER_INVALID_SCALE"; + case ANDROID_IMAGE_DECODER_BAD_PARAMETER: + return "ANDROID_IMAGE_DECODER_BAD_PARAMETER"; + case ANDROID_IMAGE_DECODER_INVALID_INPUT: + return "ANDROID_IMAGE_DECODER_INVALID_INPUT"; + case ANDROID_IMAGE_DECODER_SEEK_ERROR: + return "ANDROID_IMAGE_DECODER_SEEK_ERROR"; + case ANDROID_IMAGE_DECODER_INTERNAL_ERROR: + return "ANDROID_IMAGE_DECODER_INTERNAL_ERROR"; + case ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT: + return "ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT"; + case ANDROID_IMAGE_DECODER_FINISHED: + return "ANDROID_IMAGE_DECODER_FINISHED"; + case ANDROID_IMAGE_DECODER_INVALID_STATE: + return "ANDROID_IMAGE_DECODER_INVALID_STATE"; + default: + return nullptr; + } +} + static int createFromStream(std::unique_ptr<SkStreamRewindable> stream, AImageDecoder** outDecoder) { SkCodec::Result result; auto codec = SkCodec::MakeFromStream(std::move(stream), &result, nullptr, @@ -173,7 +204,13 @@ int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) || format > ANDROID_BITMAP_FORMAT_RGBA_F16) { return ANDROID_IMAGE_DECODER_BAD_PARAMETER; } - return toDecoder(decoder)->setOutColorType(getColorType((AndroidBitmapFormat) format)) + + auto* imageDecoder = toDecoder(decoder); + if (imageDecoder->currentFrame() != 0) { + return ANDROID_IMAGE_DECODER_INVALID_STATE; + } + + return imageDecoder->setOutColorType(getColorType((AndroidBitmapFormat) format)) ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION; } @@ -185,6 +222,10 @@ int AImageDecoder_setDataSpace(AImageDecoder* decoder, int32_t dataspace) { } ImageDecoder* imageDecoder = toDecoder(decoder); + if (imageDecoder->currentFrame() != 0) { + return ANDROID_IMAGE_DECODER_INVALID_STATE; + } + imageDecoder->setOutColorSpace(std::move(cs)); return ANDROID_IMAGE_DECODER_SUCCESS; } @@ -279,7 +320,12 @@ int AImageDecoder_setUnpremultipliedRequired(AImageDecoder* decoder, bool requir return ANDROID_IMAGE_DECODER_BAD_PARAMETER; } - return toDecoder(decoder)->setUnpremultipliedRequired(required) + auto* imageDecoder = toDecoder(decoder); + if (imageDecoder->currentFrame() != 0) { + return ANDROID_IMAGE_DECODER_INVALID_STATE; + } + + return imageDecoder->setUnpremultipliedRequired(required) ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION; } @@ -288,7 +334,12 @@ int AImageDecoder_setTargetSize(AImageDecoder* decoder, int32_t width, int32_t h return ANDROID_IMAGE_DECODER_BAD_PARAMETER; } - return toDecoder(decoder)->setTargetSize(width, height) + auto* imageDecoder = toDecoder(decoder); + if (imageDecoder->currentFrame() != 0) { + return ANDROID_IMAGE_DECODER_INVALID_STATE; + } + + return imageDecoder->setTargetSize(width, height) ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_SCALE; } @@ -309,10 +360,15 @@ int AImageDecoder_setCrop(AImageDecoder* decoder, ARect crop) { return ANDROID_IMAGE_DECODER_BAD_PARAMETER; } + auto* imageDecoder = toDecoder(decoder); + if (imageDecoder->currentFrame() != 0) { + return ANDROID_IMAGE_DECODER_INVALID_STATE; + } + SkIRect cropIRect; cropIRect.setLTRB(crop.left, crop.top, crop.right, crop.bottom); SkIRect* cropPtr = cropIRect == SkIRect::MakeEmpty() ? nullptr : &cropIRect; - return toDecoder(decoder)->setCropRect(cropPtr) + return imageDecoder->setCropRect(cropPtr) ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_BAD_PARAMETER; } @@ -341,6 +397,10 @@ int AImageDecoder_decodeImage(AImageDecoder* decoder, return ANDROID_IMAGE_DECODER_BAD_PARAMETER; } + if (imageDecoder->finished()) { + return ANDROID_IMAGE_DECODER_FINISHED; + } + return ResultToErrorCode(imageDecoder->decode(pixels, stride)); } @@ -352,7 +412,7 @@ bool AImageDecoder_isAnimated(AImageDecoder* decoder) { if (!decoder) return false; ImageDecoder* imageDecoder = toDecoder(decoder); - return imageDecoder->mCodec->codec()->getFrameCount() > 1; + return imageDecoder->isAnimated(); } int32_t AImageDecoder_getRepeatCount(AImageDecoder* decoder) { @@ -369,3 +429,109 @@ int32_t AImageDecoder_getRepeatCount(AImageDecoder* decoder) { } return count; } + +int AImageDecoder_advanceFrame(AImageDecoder* decoder) { + if (!decoder) return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + + ImageDecoder* imageDecoder = toDecoder(decoder); + if (!imageDecoder->isAnimated()) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + if (imageDecoder->advanceFrame()) { + return ANDROID_IMAGE_DECODER_SUCCESS; + } + + if (imageDecoder->finished()) { + return ANDROID_IMAGE_DECODER_FINISHED; + } + + return ANDROID_IMAGE_DECODER_INCOMPLETE; +} + +int AImageDecoder_rewind(AImageDecoder* decoder) { + if (!decoder) return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + + ImageDecoder* imageDecoder = toDecoder(decoder); + if (!imageDecoder->isAnimated()) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + return imageDecoder->rewind() ? ANDROID_IMAGE_DECODER_SUCCESS + : ANDROID_IMAGE_DECODER_SEEK_ERROR; +} + +AImageDecoderFrameInfo* AImageDecoderFrameInfo_create() { + return reinterpret_cast<AImageDecoderFrameInfo*>(new SkCodec::FrameInfo); +} + +static SkCodec::FrameInfo* toFrameInfo(AImageDecoderFrameInfo* info) { + return reinterpret_cast<SkCodec::FrameInfo*>(info); +} + +static const SkCodec::FrameInfo* toFrameInfo(const AImageDecoderFrameInfo* info) { + return reinterpret_cast<const SkCodec::FrameInfo*>(info); +} + +void AImageDecoderFrameInfo_delete(AImageDecoderFrameInfo* info) { + delete toFrameInfo(info); +} + +int AImageDecoder_getFrameInfo(AImageDecoder* decoder, + AImageDecoderFrameInfo* info) { + if (!decoder || !info) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + auto* imageDecoder = toDecoder(decoder); + if (imageDecoder->finished()) { + return ANDROID_IMAGE_DECODER_FINISHED; + } + + *toFrameInfo(info) = imageDecoder->getCurrentFrameInfo(); + return ANDROID_IMAGE_DECODER_SUCCESS; +} + +int64_t AImageDecoderFrameInfo_getDuration(const AImageDecoderFrameInfo* info) { + if (!info) return 0; + + return toFrameInfo(info)->fDuration * 1'000'000; +} + +ARect AImageDecoderFrameInfo_getFrameRect(const AImageDecoderFrameInfo* info) { + if (!info) { + return { 0, 0, 0, 0}; + } + + const SkIRect& r = toFrameInfo(info)->fFrameRect; + return { r.left(), r.top(), r.right(), r.bottom() }; +} + +bool AImageDecoderFrameInfo_hasAlphaWithinBounds(const AImageDecoderFrameInfo* info) { + if (!info) return false; + + return toFrameInfo(info)->fHasAlphaWithinBounds; +} + +int32_t AImageDecoderFrameInfo_getDisposeOp(const AImageDecoderFrameInfo* info) { + if (!info) return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + + static_assert(static_cast<int>(SkCodecAnimation::DisposalMethod::kKeep) + == ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE); + static_assert(static_cast<int>(SkCodecAnimation::DisposalMethod::kRestoreBGColor) + == ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND); + static_assert(static_cast<int>(SkCodecAnimation::DisposalMethod::kRestorePrevious) + == ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS); + return static_cast<int>(toFrameInfo(info)->fDisposalMethod); +} + +int32_t AImageDecoderFrameInfo_getBlendOp(const AImageDecoderFrameInfo* info) { + if (!info) return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + + switch (toFrameInfo(info)->fBlend) { + case SkCodecAnimation::Blend::kSrc: + return ANDROID_IMAGE_DECODER_BLEND_OP_SRC; + case SkCodecAnimation::Blend::kSrcOver: + return ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER; + } +} diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt index a184ab9d42e9..d8c3cefd30ca 100644 --- a/native/graphics/jni/libjnigraphics.map.txt +++ b/native/graphics/jni/libjnigraphics.map.txt @@ -1,5 +1,6 @@ LIBJNIGRAPHICS { global: + AImageDecoder_resultToString; # introduced=31 AImageDecoder_createFromAAsset; # introduced=30 AImageDecoder_createFromFd; # introduced=30 AImageDecoder_createFromBuffer; # introduced=30 @@ -15,12 +16,22 @@ LIBJNIGRAPHICS { AImageDecoder_setCrop; # introduced=30 AImageDecoder_isAnimated; # introduced=31 AImageDecoder_getRepeatCount; # introduced=31 + AImageDecoder_advanceFrame; # introduced=31 + AImageDecoder_rewind; # introduced=31 + AImageDecoder_getFrameInfo; # introduced = 31 AImageDecoderHeaderInfo_getWidth; # introduced=30 AImageDecoderHeaderInfo_getHeight; # introduced=30 AImageDecoderHeaderInfo_getMimeType; # introduced=30 AImageDecoderHeaderInfo_getAlphaFlags; # introduced=30 AImageDecoderHeaderInfo_getAndroidBitmapFormat; # introduced=30 AImageDecoderHeaderInfo_getDataSpace; # introduced=30 + AImageDecoderFrameInfo_create; # introduced = 31 + AImageDecoderFrameInfo_delete; # introduced = 31 + AImageDecoderFrameInfo_getDuration; # introduced = 31 + AImageDecoderFrameInfo_getFrameRect; # introduced = 31 + AImageDecoderFrameInfo_hasAlphaWithinBounds; # introduced = 31 + AImageDecoderFrameInfo_getDisposeOp; # introduced = 31 + AImageDecoderFrameInfo_getBlendOp; # introduced = 31 AndroidBitmap_getInfo; AndroidBitmap_getDataSpace; AndroidBitmap_lockPixels; diff --git a/native/webview/TEST_MAPPING b/native/webview/TEST_MAPPING new file mode 100644 index 000000000000..bd25200ffc38 --- /dev/null +++ b/native/webview/TEST_MAPPING @@ -0,0 +1,36 @@ +{ + "presubmit": [ + { + "name": "CtsWebkitTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "CtsHostsideWebViewTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "GtsWebViewTestCases", + "options": [ + { + "exclude-annotation": "android.test.FlakyTest" + } + ] + }, + { + "name": "GtsWebViewHostTestCases", + "options": [ + { + "exclude-annotation": "android.test.FlakyTest" + } + ] + } + ] +} diff --git a/packages/CompanionDeviceManager/res/values-af/strings.xml b/packages/CompanionDeviceManager/res/values-af/strings.xml index b83c914f4b46..8fc353191441 100644 --- a/packages/CompanionDeviceManager/res/values-af/strings.xml +++ b/packages/CompanionDeviceManager/res/values-af/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Metgeseltoestel-bestuurder"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Kies \'n <xliff:g id="PROFILE_NAME">%1$s</xliff:g> om deur <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> bestuur te word"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"toestel"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Stel <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> om jou <xliff:g id="PROFILE_NAME">%2$s</xliff:g> te bestuur – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Jy het <xliff:g id="APP_NAME_0">%1$s</xliff:g> nodig om jou <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> te bestuur. <xliff:g id="APP_NAME2">%3$s</xliff:g> sal toegang hê tot <xliff:g id="PERMISSIONS">%4$s</xliff:g> terwyl die <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> gekoppel is."</string> + <string name="consent_yes" msgid="4055438216605487056">"Ja"</string> + <string name="consent_no" msgid="1335543792857823917">"Nee, dankie"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml index bad5a9fff6c6..ff31454cba08 100644 --- a/packages/CompanionDeviceManager/res/values-am/strings.xml +++ b/packages/CompanionDeviceManager/res/values-am/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"አጃቢ የመሣሪያ አስተዳዳሪ"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"በ<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> የሚተዳደር <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ይምረጡ"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"መሣሪያ"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> የእርስዎን <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> እንዲያስተዳድር ያቀናብሩት"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> የእርስዎን <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> ለማስተዳደር ያስፈልጋል። <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> ተገናኝቶ ሳለ <xliff:g id="APP_NAME2">%3$s</xliff:g> የ<xliff:g id="PERMISSIONS">%4$s</xliff:g> መዳረሻን ያገኛል።"</string> + <string name="consent_yes" msgid="4055438216605487056">"አዎ"</string> + <string name="consent_no" msgid="1335543792857823917">"አይ፣ አመሰግናለሁ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml index a9a202e853f3..aedf0f3f94b2 100644 --- a/packages/CompanionDeviceManager/res/values-ar/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"تطبيق \"مدير الجهاز المصاحب\""</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"اختَر <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ليديره تطبيق <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"جهاز"</string> + <string name="confirmation_title" msgid="4751119145078041732">"اضبط <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> لإدارة <xliff:g id="PROFILE_NAME">%2$s</xliff:g> على <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"يجب توفّر تطبيق <xliff:g id="APP_NAME_0">%1$s</xliff:g> لإدارة <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. سيتمكن تطبيق <xliff:g id="APP_NAME2">%3$s</xliff:g> من الوصول إلى <xliff:g id="PERMISSIONS">%4$s</xliff:g> عندما يكون <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> مرتبطًا."</string> + <string name="consent_yes" msgid="4055438216605487056">"نعم"</string> + <string name="consent_no" msgid="1335543792857823917">"لا، شكرًا"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-as/strings.xml b/packages/CompanionDeviceManager/res/values-as/strings.xml index 31db473298a0..f0c3ee890a41 100644 --- a/packages/CompanionDeviceManager/res/values-as/strings.xml +++ b/packages/CompanionDeviceManager/res/values-as/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"কম্পেনিয়ন ডিভাইচ মেনেজাৰ"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>এ পৰিচালনা কৰিব লগা এটা <xliff:g id="PROFILE_NAME">%1$s</xliff:g> বাছনি কৰক"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"ডিভাইচ"</string> + <string name="confirmation_title" msgid="4751119145078041732">"আপোনাৰ <xliff:g id="PROFILE_NAME">%2$s</xliff:g> পৰিচালনা কৰিবলৈ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ছেট কৰক - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"আপোনাৰ <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> পৰিচালনা কৰিবলৈ <xliff:g id="APP_NAME_0">%1$s</xliff:g>ৰ আৱশ্যক। <xliff:g id="PROFILE_NAME2">%5$s</xliff:g>ৰ সৈতে সংযোগ কৰিলে <xliff:g id="APP_NAME2">%3$s</xliff:g>এ <xliff:g id="PERMISSIONS">%4$s</xliff:g>লৈ এক্সেছ পাব।"</string> + <string name="consent_yes" msgid="4055438216605487056">"হয়"</string> + <string name="consent_no" msgid="1335543792857823917">"নালাগে, ধন্যবাদ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-az/strings.xml b/packages/CompanionDeviceManager/res/values-az/strings.xml index ee794c36e630..64bea4da663b 100644 --- a/packages/CompanionDeviceManager/res/values-az/strings.xml +++ b/packages/CompanionDeviceManager/res/values-az/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Kompanyon Cihaz Meneceri"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> tərəfindən idarə ediləcək <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<xliff:g id="PROFILE_NAME">%2$s</xliff:g> profilinizin <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> tərəfindən idarə olunmasını ayarlayın - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> profilinizi idarə etmək üçün <xliff:g id="APP_NAME_0">%1$s</xliff:g> tələb olunur. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> qoşulu olduqda <xliff:g id="APP_NAME2">%3$s</xliff:g> <xliff:g id="PERMISSIONS">%4$s</xliff:g> bölməsinə giriş əldə edəcək."</string> + <string name="consent_yes" msgid="4055438216605487056">"Bəli"</string> + <string name="consent_no" msgid="1335543792857823917">"Xeyr, çox sağolun"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml index 708e49a55ef6..3f067227fa47 100644 --- a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Menadžer pridruženog uređaja"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Podesite aplikaciju <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> da upravlja profilom <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Aplikacija <xliff:g id="APP_NAME_0">%1$s</xliff:g> je neophodna za upravljanje profilom <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> će dobiti pristup dozvolama za <xliff:g id="PERMISSIONS">%4$s</xliff:g> dok je <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> povezan."</string> + <string name="consent_yes" msgid="4055438216605487056">"Da"</string> + <string name="consent_no" msgid="1335543792857823917">"Ne, hvala"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml index a1a04e671b6b..25e235c98106 100644 --- a/packages/CompanionDeviceManager/res/values-be/strings.xml +++ b/packages/CompanionDeviceManager/res/values-be/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Менеджар спадарожнай прылады"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Выберыце прыладу (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), якой будзе кіраваць праграма <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"прылада"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Дазвольце праграме <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> кіраваць прыладай \"<xliff:g id="PROFILE_NAME">%2$s</xliff:g>\" – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Для кіравання прыладай \"<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>\" патрабуецца праграма \"<xliff:g id="APP_NAME_0">%1$s</xliff:g>\". Калі прылада \"<xliff:g id="PROFILE_NAME2">%5$s</xliff:g>\" будзе падключана, <xliff:g id="APP_NAME2">%3$s</xliff:g> атрымае наступныя дазволы: <xliff:g id="PERMISSIONS">%4$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Так"</string> + <string name="consent_no" msgid="1335543792857823917">"Не, дзякуй"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml index eb9204ace00d..264ce27b76d6 100644 --- a/packages/CompanionDeviceManager/res/values-bg/strings.xml +++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Изберете устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), което да се управлява от <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Задайте <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> да управлява устройството ви (<xliff:g id="PROFILE_NAME">%2$s</xliff:g>) – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"За управление на <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> се изисква <xliff:g id="APP_NAME_0">%1$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> ще получи достъп до <xliff:g id="PERMISSIONS">%4$s</xliff:g>, докато устройството (<xliff:g id="PROFILE_NAME2">%5$s</xliff:g>) е свързано."</string> + <string name="consent_yes" msgid="4055438216605487056">"Да"</string> + <string name="consent_no" msgid="1335543792857823917">"Не, благодаря"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-bn/strings.xml b/packages/CompanionDeviceManager/res/values-bn/strings.xml index eb9204ace00d..65f92c908b86 100644 --- a/packages/CompanionDeviceManager/res/values-bn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-bn/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> বেছে নিন যেটি <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ম্যানেজ করবে"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"ডিভাইস"</string> + <string name="confirmation_title" msgid="4751119145078041732">"আপনার <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> ম্যানেজ করার জন্য <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> সেট করুন"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g>-কে আপনার <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>.ম্যানেজ করতে দিতে হবে। <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> কানেক্ট করা হলে <xliff:g id="APP_NAME2">%3$s</xliff:g> <xliff:g id="PERMISSIONS">%4$s</xliff:g> অ্যাক্সেস করতে পারবে।"</string> + <string name="consent_yes" msgid="4055438216605487056">"হ্যাঁ"</string> + <string name="consent_no" msgid="1335543792857823917">"না থাক"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-bs/strings.xml b/packages/CompanionDeviceManager/res/values-bs/strings.xml index 6515981fa5f9..f8e24b7a1cf4 100644 --- a/packages/CompanionDeviceManager/res/values-bs/strings.xml +++ b/packages/CompanionDeviceManager/res/values-bs/strings.xml @@ -17,10 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Prateći upravitelj uređaja"</string> - <string name="chooser_title" msgid="2262294130493605839">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="chooser_title" msgid="2262294130493605839">"Odaberite uređaj <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string> - <string name="confirmation_title" msgid="4751119145078041732">"Postavite aplikaciju <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> da upravlja vašim profilom <xliff:g id="PROFILE_NAME">%2$s</xliff:g> –- <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> - <string name="profile_summary" msgid="3167701603666642104">"Potrebna je <xliff:g id="APP_NAME_0">%1$s</xliff:g> za upravljanje vašim profilom <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME2">%3$s</xliff:g> dobit će pristup dopuštenju za <xliff:g id="PERMISSIONS">%4$s</xliff:g> dok je povezan profil <xliff:g id="PROFILE_NAME2">%5$s</xliff:g>."</string> + <string name="confirmation_title" msgid="4751119145078041732">"Postavite aplikaciju <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> da upravlja vašim uređajem <xliff:g id="PROFILE_NAME">%2$s</xliff:g> — <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Potrebna je aplikacija <xliff:g id="APP_NAME_0">%1$s</xliff:g> za upravljanje uređajem <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME2">%3$s</xliff:g> će dobiti pristup uslugama <xliff:g id="PERMISSIONS">%4$s</xliff:g> dok je uređaj <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> povezan."</string> <string name="consent_yes" msgid="4055438216605487056">"Da"</string> <string name="consent_no" msgid="1335543792857823917">"Ne, hvala"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ca/strings.xml b/packages/CompanionDeviceManager/res/values-ca/strings.xml index a51c9675c5cc..ae8ca9f7efab 100644 --- a/packages/CompanionDeviceManager/res/values-ca/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ca/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Gestor de dispositius complementaris"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Tria un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> perquè el gestioni <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"dispositiu"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Defineix que <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> gestioni el teu <xliff:g id="PROFILE_NAME">%2$s</xliff:g> (<strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>)"</string> + <string name="profile_summary" msgid="3167701603666642104">"Cal <xliff:g id="APP_NAME_0">%1$s</xliff:g> per gestionar el teu <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> tindrà accés als permisos <xliff:g id="PERMISSIONS">%4$s</xliff:g> mentre el <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> estigui connectat."</string> + <string name="consent_yes" msgid="4055438216605487056">"Sí"</string> + <string name="consent_no" msgid="1335543792857823917">"No, gràcies"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-cs/strings.xml b/packages/CompanionDeviceManager/res/values-cs/strings.xml index faed4dd18a7f..675bd2945382 100644 --- a/packages/CompanionDeviceManager/res/values-cs/strings.xml +++ b/packages/CompanionDeviceManager/res/values-cs/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Správce doprovodných zařízení"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Vyberte zařízení <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, které chcete spravovat pomocí aplikace <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"zařízení"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Nastavit aplikaci <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ke správě tohoto zařízení: <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Ke správě zařízení <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> je potřeba aplikace <xliff:g id="APP_NAME_0">%1$s</xliff:g>. Dokud bude zařízení <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> připojeno, bude mít aplikace <xliff:g id="APP_NAME2">%3$s</xliff:g> přístup k těmto oprávněním: <xliff:g id="PERMISSIONS">%4$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Ano"</string> + <string name="consent_no" msgid="1335543792857823917">"Ne, díky"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-da/strings.xml b/packages/CompanionDeviceManager/res/values-da/strings.xml index c87181a4ca9d..a6720fca2df7 100644 --- a/packages/CompanionDeviceManager/res/values-da/strings.xml +++ b/packages/CompanionDeviceManager/res/values-da/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Medfølgende enhedshåndtering"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Vælg den enhed (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), som skal administreres af <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"enhed"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Angiv <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> til administration af: <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> er nødvendig for at administrere: <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> får adgang til <xliff:g id="PERMISSIONS">%4$s</xliff:g>, mens <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> er forbundet."</string> + <string name="consent_yes" msgid="4055438216605487056">"Ja"</string> + <string name="consent_no" msgid="1335543792857823917">"Nej tak"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-de/strings.xml b/packages/CompanionDeviceManager/res/values-de/strings.xml index 239243ae7416..eb2631f2f425 100644 --- a/packages/CompanionDeviceManager/res/values-de/strings.xml +++ b/packages/CompanionDeviceManager/res/values-de/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Begleitgerät-Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Gerät (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) auswählen, das von <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> verwaltet werden soll"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"Gerät"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> zum Verwalten deines Geräts (<xliff:g id="PROFILE_NAME">%2$s</xliff:g>) festlegen – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ist erforderlich, um dein Gerät (<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>) zu verwalten. <xliff:g id="APP_NAME2">%3$s</xliff:g> erhält Zugriff auf <xliff:g id="PERMISSIONS">%4$s</xliff:g>, während eine Verbindung mit <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> besteht."</string> + <string name="consent_yes" msgid="4055438216605487056">"Ja"</string> + <string name="consent_no" msgid="1335543792857823917">"Nein danke"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-el/strings.xml b/packages/CompanionDeviceManager/res/values-el/strings.xml index 0738161cc9e0..cb31866cc36f 100644 --- a/packages/CompanionDeviceManager/res/values-el/strings.xml +++ b/packages/CompanionDeviceManager/res/values-el/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Διαχείριση συνοδευτικής εφαρμογής"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Επιλέξτε ένα προφίλ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> για διαχείριση από την εφαρμογή <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"συσκευή"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Ορίστε μια εφαρμογή <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> για διαχείριση του προφίλ <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Η εφαρμογή <xliff:g id="APP_NAME_0">%1$s</xliff:g> απαιτείται για τη διαχείριση του προφίλ <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. Η εφαρμογή <xliff:g id="APP_NAME2">%3$s</xliff:g> θα έχει πρόσβαση στις άδειες <xliff:g id="PERMISSIONS">%4$s</xliff:g> ενώ είναι συνδεδεμένο το προφίλ <xliff:g id="PROFILE_NAME2">%5$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Ναι"</string> + <string name="consent_no" msgid="1335543792857823917">"Όχι, ευχαριστώ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml index 579639f009f7..63097dc786ff 100644 --- a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml +++ b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Administrador de dispositivo complementario"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para que <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> lo administre"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Configura <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para administrar <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Se requiere <xliff:g id="APP_NAME_0">%1$s</xliff:g> para administrar tu <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> accederá a <xliff:g id="PERMISSIONS">%4$s</xliff:g> mientras tu <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> esté conectado."</string> + <string name="consent_yes" msgid="4055438216605487056">"Sí"</string> + <string name="consent_no" msgid="1335543792857823917">"No, gracias"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml index 4192f5ea47d4..784c30d48e36 100644 --- a/packages/CompanionDeviceManager/res/values-es/strings.xml +++ b/packages/CompanionDeviceManager/res/values-es/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Gestor de dispositivos complementario"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para gestionarlo con <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Haz que <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> gestione tu <xliff:g id="PROFILE_NAME">%2$s</xliff:g> <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Se necesita <xliff:g id="APP_NAME_0">%1$s</xliff:g> para gestionar tu <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. Mientras tu <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> esté conectado, <xliff:g id="APP_NAME2">%3$s</xliff:g> tendrá acceso a lo siguiente: <xliff:g id="PERMISSIONS">%4$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Sí"</string> + <string name="consent_no" msgid="1335543792857823917">"No, gracias"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-et/strings.xml b/packages/CompanionDeviceManager/res/values-et/strings.xml index 5432ab3c20db..b5058701cc8a 100644 --- a/packages/CompanionDeviceManager/res/values-et/strings.xml +++ b/packages/CompanionDeviceManager/res/values-et/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Kaasseadme haldur"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Valige seade <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, mida haldab rakendus <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"seade"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Määrake rakendus <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> haldama teie seadet <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> on vajalik teie seadme <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> haldamiseks. <xliff:g id="APP_NAME2">%3$s</xliff:g> saab juurdepääsuload (<xliff:g id="PERMISSIONS">%4$s</xliff:g>), kui seade <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> on ühendatud."</string> + <string name="consent_yes" msgid="4055438216605487056">"Jah"</string> + <string name="consent_no" msgid="1335543792857823917">"Tänan, ei"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml index 6e9729bddd05..826556da33de 100644 --- a/packages/CompanionDeviceManager/res/values-eu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Gailu osagarriaren kudeatzailea"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Aukeratu <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> aplikazioak kudeatu beharreko <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"gailua"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Konfiguratu <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> <xliff:g id="PROFILE_NAME">%2$s</xliff:g> (<strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>) kudea dezan"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> erabili behar duzu <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> kudeatzeko. <xliff:g id="APP_NAME2">%3$s</xliff:g> aplikazioak <xliff:g id="PERMISSIONS">%4$s</xliff:g> atzitu ahalko ditu <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> konektatuta dagoen bitartean."</string> + <string name="consent_yes" msgid="4055438216605487056">"Bai"</string> + <string name="consent_no" msgid="1335543792857823917">"Ez"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml index c1d951ea07b8..66eafc3f320f 100644 --- a/packages/CompanionDeviceManager/res/values-fa/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"مدیر دستگاه مرتبط"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"انتخاب <xliff:g id="PROFILE_NAME">%1$s</xliff:g> برای مدیریت کردن با <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"دستگاه"</string> + <string name="confirmation_title" msgid="4751119145078041732">"تنظیم <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> برای مدیریت کردن <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"برای مدیریت کردن <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> به <xliff:g id="APP_NAME_0">%1$s</xliff:g> نیاز دارید. وقتی <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> متصل باشد، <xliff:g id="APP_NAME2">%3$s</xliff:g> به <xliff:g id="PERMISSIONS">%4$s</xliff:g> دسترسی پیدا خواهد کرد."</string> + <string name="consent_yes" msgid="4055438216605487056">"بله"</string> + <string name="consent_no" msgid="1335543792857823917">"نه متشکرم"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-fi/strings.xml b/packages/CompanionDeviceManager/res/values-fi/strings.xml index eb9204ace00d..d4a20d9421f1 100644 --- a/packages/CompanionDeviceManager/res/values-fi/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fi/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Valitse <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, jota <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> hallinnoi"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"laite"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Aseta <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> profiilin (<xliff:g id="PROFILE_NAME">%2$s</xliff:g>) hallinnoijaksi – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> tarvitaan profiilin (<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>) hallinnointiin. <xliff:g id="APP_NAME2">%3$s</xliff:g> saa käyttöluvat (<xliff:g id="PERMISSIONS">%4$s</xliff:g>), kun <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> on yhdistetty."</string> + <string name="consent_yes" msgid="4055438216605487056">"Kyllä"</string> + <string name="consent_no" msgid="1335543792857823917">"Ei kiitos"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml index 3c8fdd1ae41e..e91ccf4de04f 100644 --- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Gestionnaire d\'appareil compagnon"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Choisissez un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré par <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Utiliser <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> pour gérer votre <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"L\'application <xliff:g id="APP_NAME_0">%1$s</xliff:g> est requise pour gérer votre <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> aura accès à <xliff:g id="PERMISSIONS">%4$s</xliff:g> lorsque <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> est connecté."</string> + <string name="consent_yes" msgid="4055438216605487056">"Oui"</string> + <string name="consent_no" msgid="1335543792857823917">"Non merci"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml index fc0e4da9158f..756727a7f3f9 100644 --- a/packages/CompanionDeviceManager/res/values-fr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Gestionnaire d\'appareils associés"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Sélectionner le/la <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré(e) par <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Définir <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> pour la gestion de votre <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> est requis pour gérer votre <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> aura accès aux <xliff:g id="PERMISSIONS">%4$s</xliff:g> lorsque le/la <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> sera connecté(e)."</string> + <string name="consent_yes" msgid="4055438216605487056">"Oui"</string> + <string name="consent_no" msgid="1335543792857823917">"Non, merci"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml index 0a4e7fe696e9..7849e42a5bf1 100644 --- a/packages/CompanionDeviceManager/res/values-gl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Xestor de dispositivos complementarios"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Escolle un perfil (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) para que o xestione a aplicación <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Define a aplicación <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para a xestión do teu perfil (<xliff:g id="PROFILE_NAME">%2$s</xliff:g>): <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Necesítase a aplicación <xliff:g id="APP_NAME_0">%1$s</xliff:g> para xestionar o teu perfil (<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>). <xliff:g id="APP_NAME2">%3$s</xliff:g> terá acceso a varios permisos (<xliff:g id="PERMISSIONS">%4$s</xliff:g>) mentres o perfil (<xliff:g id="PROFILE_NAME2">%5$s</xliff:g>) estea conectado."</string> + <string name="consent_yes" msgid="4055438216605487056">"Si"</string> + <string name="consent_no" msgid="1335543792857823917">"Non, grazas"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml index bee2e7719eed..1f1edd14b967 100644 --- a/packages/CompanionDeviceManager/res/values-gu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"કમ્પેનિયન ડિવાઇસ મેનેજર"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> દ્વારા મેનેજ કરવા માટે કોઈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> પસંદ કરો"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"ડિવાઇસ"</string> + <string name="confirmation_title" msgid="4751119145078041732">"તમારા <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>ને મેનેજ કરવા માટે <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> સેટ કરો"</string> + <string name="profile_summary" msgid="3167701603666642104">"તમારા <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>ને મેનેજ કરવા માટે <xliff:g id="APP_NAME_0">%1$s</xliff:g> જરૂરી છે. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> જ્યારે કનેક્ટેડ હોય ત્યારે <xliff:g id="APP_NAME2">%3$s</xliff:g>ને <xliff:g id="PERMISSIONS">%4$s</xliff:g>નો ઍક્સેસ મળશે."</string> + <string name="consent_yes" msgid="4055438216605487056">"હા"</string> + <string name="consent_no" msgid="1335543792857823917">"ના, આભાર"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml index 46bb4cee791c..6e280b551b24 100644 --- a/packages/CompanionDeviceManager/res/values-hi/strings.xml +++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"सहयोगी डिवाइस मैनेजर"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"कोई <xliff:g id="PROFILE_NAME">%1$s</xliff:g> चुनें, ताकि उसे <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> की मदद से प्रबंधित किया जा सके"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"डिवाइस"</string> + <string name="confirmation_title" msgid="4751119145078041732">"अपने <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> को प्रबंधित करने के लिए, <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> को सेट करें"</string> + <string name="profile_summary" msgid="3167701603666642104">"आपके <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> को प्रबंधित करने के लिए, <xliff:g id="APP_NAME_0">%1$s</xliff:g> की ज़रूरत है. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> के कनेक्ट होने पर, <xliff:g id="APP_NAME2">%3$s</xliff:g> को <xliff:g id="PERMISSIONS">%4$s</xliff:g> का ऐक्सेस मिल जाएगा."</string> + <string name="consent_yes" msgid="4055438216605487056">"हां"</string> + <string name="consent_no" msgid="1335543792857823917">"नहीं, रहने दें"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-hy/strings.xml b/packages/CompanionDeviceManager/res/values-hy/strings.xml index eb9204ace00d..b0037aa74c2d 100644 --- a/packages/CompanionDeviceManager/res/values-hy/strings.xml +++ b/packages/CompanionDeviceManager/res/values-hy/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Ընտրեք <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ը, որը պետք է կառավարվի <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> հավելվածի կողմից"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"սարք"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Ընտրեք <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> հավելվածը որպես <xliff:g id="PROFILE_NAME">%2$s</xliff:g>ի կառավարիչ․ <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Ձեր <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>ը կառավարելու համար անհրաժեշտ է <xliff:g id="APP_NAME_0">%1$s</xliff:g> հավելվածը։ Երբ <xliff:g id="PROFILE_NAME2">%5$s</xliff:g>ը միացված լինի, <xliff:g id="APP_NAME2">%3$s</xliff:g> հավելվածին հասանելի կլինեն՝ <xliff:g id="PERMISSIONS">%4$s</xliff:g>։"</string> + <string name="consent_yes" msgid="4055438216605487056">"Այո"</string> + <string name="consent_no" msgid="1335543792857823917">"Ոչ, շնորհակալություն"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-in/strings.xml b/packages/CompanionDeviceManager/res/values-in/strings.xml index 71159611e2fc..cc05490981b4 100644 --- a/packages/CompanionDeviceManager/res/values-in/strings.xml +++ b/packages/CompanionDeviceManager/res/values-in/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Pengelola Perangkat Pendamping"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk dikelola oleh <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"perangkat"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Tetapkan <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> untuk mengelola <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> diperlukan untuk mengelola <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> akan mendapatkan akses ke <xliff:g id="PERMISSIONS">%4$s</xliff:g> saat <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> terhubung."</string> + <string name="consent_yes" msgid="4055438216605487056">"Ya"</string> + <string name="consent_no" msgid="1335543792857823917">"Tidak"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml index 615ce83dd541..d3ada7427553 100644 --- a/packages/CompanionDeviceManager/res/values-is/strings.xml +++ b/packages/CompanionDeviceManager/res/values-is/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Stjórnun fylgdartækja"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Velja <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sem <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> á að stjórna"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"tæki"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Veita <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> stjórn á <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> er krafist til að stjórna <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> fær aðgang að <xliff:g id="PERMISSIONS">%4$s</xliff:g> þegar <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> er tengt."</string> + <string name="consent_yes" msgid="4055438216605487056">"Já"</string> + <string name="consent_no" msgid="1335543792857823917">"Nei, takk"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml index 05154ee4a951..ae9d6f483150 100644 --- a/packages/CompanionDeviceManager/res/values-iw/strings.xml +++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"ניהול מכשיר מותאם"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"בחירה של <xliff:g id="PROFILE_NAME">%1$s</xliff:g> לניהול באמצעות <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"מכשיר"</string> + <string name="confirmation_title" msgid="4751119145078041732">"הגדרה של <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> לניהול <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"האפליקציה <xliff:g id="APP_NAME_0">%1$s</xliff:g> נדרשת לניהול של <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. האפליקציה <xliff:g id="APP_NAME2">%3$s</xliff:g> תקבל גישה אל <xliff:g id="PERMISSIONS">%4$s</xliff:g> כאשר <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> מחובר."</string> + <string name="consent_yes" msgid="4055438216605487056">"כן"</string> + <string name="consent_no" msgid="1335543792857823917">"לא תודה"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml index 76fd4b6db3fc..3fb0140bb702 100644 --- a/packages/CompanionDeviceManager/res/values-ja/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"コンパニオン デバイス マネージャ"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> の管理対象となる <xliff:g id="PROFILE_NAME">%1$s</xliff:g> の選択"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"デバイス"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> で <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> を管理するよう設定する"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> を管理するために <xliff:g id="APP_NAME_0">%1$s</xliff:g> が必要です。<xliff:g id="PROFILE_NAME2">%5$s</xliff:g> の接続中に、<xliff:g id="APP_NAME2">%3$s</xliff:g> が <xliff:g id="PERMISSIONS">%4$s</xliff:g> にアクセスします。"</string> + <string name="consent_yes" msgid="4055438216605487056">"はい"</string> + <string name="consent_no" msgid="1335543792857823917">"いいえ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ka/strings.xml b/packages/CompanionDeviceManager/res/values-ka/strings.xml index df70f08b095f..5b36106f3baf 100644 --- a/packages/CompanionDeviceManager/res/values-ka/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ka/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"კომპანიონი მოწყობილობების მენეჯერი"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"აირჩიეთ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, რომელიც უნდა მართოს <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>-მა"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"მოწყობილობა"</string> + <string name="confirmation_title" msgid="4751119145078041732">"დააყენეთ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>, რომ მართოს თქვენი <xliff:g id="PROFILE_NAME">%2$s</xliff:g> — <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"თქვენი <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>-ის სამართავად საჭიროა <xliff:g id="APP_NAME_0">%1$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> მიიღებს წვდომას <xliff:g id="PERMISSIONS">%4$s</xliff:g>-ზე, სანამ <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> დაკავშირებული იქნება."</string> + <string name="consent_yes" msgid="4055438216605487056">"დიახ"</string> + <string name="consent_no" msgid="1335543792857823917">"არა, გმადლობთ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-kk/strings.xml b/packages/CompanionDeviceManager/res/values-kk/strings.xml index eb9204ace00d..6ff3f830e2db 100644 --- a/packages/CompanionDeviceManager/res/values-kk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-kk/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> арқылы басқарылатын <xliff:g id="PROFILE_NAME">%1$s</xliff:g> құрылғысын таңдаңыз"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"құрылғы"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> қолданбасына <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> (<xliff:g id="PROFILE_NAME">%2$s</xliff:g>) құрылғысын басқаруға рұқсат беру"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> құрылғысын басқару үшін <xliff:g id="APP_NAME_0">%1$s</xliff:g> қолданбасы керек. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> құрылғысы жалғанған кезде<xliff:g id="APP_NAME2">%3$s</xliff:g> қолданбасы мына параметрлерді пайдалана алады: <xliff:g id="PERMISSIONS">%4$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Иә"</string> + <string name="consent_no" msgid="1335543792857823917">"Жоқ, рақмет"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-km/strings.xml b/packages/CompanionDeviceManager/res/values-km/strings.xml index 8c1de45a6c79..cdcebada0709 100644 --- a/packages/CompanionDeviceManager/res/values-km/strings.xml +++ b/packages/CompanionDeviceManager/res/values-km/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"កម្មវិធីគ្រប់គ្រងឧបករណ៍ដៃគូ"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"ជ្រើសរើស <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ដើម្បីឱ្យស្ថិតក្រោមការគ្រប់គ្រងរបស់ <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"ឧបករណ៍"</string> + <string name="confirmation_title" msgid="4751119145078041732">"កំណត់ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ដើម្បីគ្រប់គ្រង <xliff:g id="PROFILE_NAME">%2$s</xliff:g> របស់អ្នក - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"ចាំបាច់ត្រូវមាន <xliff:g id="APP_NAME_0">%1$s</xliff:g> ដើម្បីគ្រប់គ្រង <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> របស់អ្នក។ <xliff:g id="APP_NAME2">%3$s</xliff:g> នឹងអាចចូលប្រើ <xliff:g id="PERMISSIONS">%4$s</xliff:g> នៅពេលភ្ជាប់ <xliff:g id="PROFILE_NAME2">%5$s</xliff:g>។"</string> + <string name="consent_yes" msgid="4055438216605487056">"បាទ/ចាស"</string> + <string name="consent_no" msgid="1335543792857823917">"ទេ អរគុណ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml index 1c3d07ba448c..47cf76cd1a11 100644 --- a/packages/CompanionDeviceManager/res/values-kn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"ಕಂಪ್ಯಾನಿಯನ್ ಸಾಧನ ನಿರ್ವಾಹಕರು"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ಮೂಲಕ ನಿರ್ವಹಿಸಬೇಕಾದ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"ಸಾಧನ"</string> + <string name="confirmation_title" msgid="4751119145078041732">"ನಿಮ್ಮ <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> ಅನ್ನು ನಿರ್ವಹಿಸಲು, <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ಅನ್ನು ನಿರ್ವಹಿಸಿ"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> ಅನ್ನು ನಿರ್ವಹಿಸಲು, <xliff:g id="APP_NAME_0">%1$s</xliff:g> ಅಗತ್ಯವಿದೆ. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> ಕನೆಕ್ಟ್ ಆದಾಗ, <xliff:g id="PERMISSIONS">%4$s</xliff:g> ಗೆ <xliff:g id="APP_NAME2">%3$s</xliff:g> ಪ್ರವೇಶವನ್ನು ಪಡೆಯುತ್ತದೆ."</string> + <string name="consent_yes" msgid="4055438216605487056">"ಹೌದು"</string> + <string name="consent_no" msgid="1335543792857823917">"ಬೇಡ, ಧನ್ಯವಾದಗಳು"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml index 7cfe8b6e8c01..f29041389686 100644 --- a/packages/CompanionDeviceManager/res/values-ko/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"부속 기기 관리자"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>에서 관리할 <xliff:g id="PROFILE_NAME">%1$s</xliff:g>을(를) 선택"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"기기"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>에서 <xliff:g id="PROFILE_NAME">%2$s</xliff:g>(<strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>)을(를) 관리하도록 설정"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>을(를) 관리하려면 <xliff:g id="APP_NAME_0">%1$s</xliff:g> 앱이 필요합니다. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g>이(가) 연결되어 있는 동안 <xliff:g id="APP_NAME2">%3$s</xliff:g> 앱이 <xliff:g id="PERMISSIONS">%4$s</xliff:g>에 액세스할 수 있습니다."</string> + <string name="consent_yes" msgid="4055438216605487056">"예"</string> + <string name="consent_no" msgid="1335543792857823917">"취소"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml index eb9204ace00d..9cce298b528c 100644 --- a/packages/CompanionDeviceManager/res/values-ky/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> тарабынан башкарылсын"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"түзмөк"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> түзмөгүңүздү башкарсын"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> профилиңизди башкаруу үчүн <xliff:g id="APP_NAME_0">%1$s</xliff:g> керек. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> туташып турганда <xliff:g id="APP_NAME2">%3$s</xliff:g> колдонмосунун төмөнкүлөргө уруксаты болот: <xliff:g id="PERMISSIONS">%4$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Ооба"</string> + <string name="consent_no" msgid="1335543792857823917">"Жок, рахмат"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-lo/strings.xml b/packages/CompanionDeviceManager/res/values-lo/strings.xml index 97d4c336ce18..5fcbf7dfd802 100644 --- a/packages/CompanionDeviceManager/res/values-lo/strings.xml +++ b/packages/CompanionDeviceManager/res/values-lo/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"ຕົວຈັດການອຸປະກອນປະກອບ"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"ເລືອກ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ເພື່ອໃຫ້ຖືກຈັດການໂດຍ <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"ອຸປະກອນ"</string> + <string name="confirmation_title" msgid="4751119145078041732">"ຕັ້ງ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ເພື່ອຈັດການ <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> ຂອງທ່ານ"</string> + <string name="profile_summary" msgid="3167701603666642104">"ຕ້ອງໃຊ້ <xliff:g id="APP_NAME_0">%1$s</xliff:g> ເພື່ອຈັດການ <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> ຂອງທ່ານ. <xliff:g id="APP_NAME2">%3$s</xliff:g> ຈະໄດ້ຮັບສິດເຂົ້າເຖິງ <xliff:g id="PERMISSIONS">%4$s</xliff:g> ໃນເວລາເຊື່ອມຕໍ່ <xliff:g id="PROFILE_NAME2">%5$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"ແມ່ນແລ້ວ"</string> + <string name="consent_no" msgid="1335543792857823917">"ບໍ່, ຂອບໃຈ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-lt/strings.xml b/packages/CompanionDeviceManager/res/values-lt/strings.xml index eb9204ace00d..56930d3edf59 100644 --- a/packages/CompanionDeviceManager/res/values-lt/strings.xml +++ b/packages/CompanionDeviceManager/res/values-lt/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Jūsų <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, kurį valdys <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> (pasirinkite)"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"įrenginys"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Nustatyti, kad <xliff:g id="PROFILE_NAME">%2$s</xliff:g> <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> būtų valdomas programos <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Tam, kad būtų valdomas jūsų <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>, reikalinga programa „<xliff:g id="APP_NAME_0">%1$s</xliff:g>“. Kol prijungtas <xliff:g id="PROFILE_NAME2">%5$s</xliff:g>, „<xliff:g id="APP_NAME2">%3$s</xliff:g>“ gaus prieigą prie šių elementų: <xliff:g id="PERMISSIONS">%4$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Taip"</string> + <string name="consent_no" msgid="1335543792857823917">"Ne, ačiū"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-lv/strings.xml b/packages/CompanionDeviceManager/res/values-lv/strings.xml index 39eafe1b382a..a9d215157c52 100644 --- a/packages/CompanionDeviceManager/res/values-lv/strings.xml +++ b/packages/CompanionDeviceManager/res/values-lv/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Palīgierīču pārzinis"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Profila (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) izvēle, ko pārvaldīt lietotnē <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"ierīce"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Lietotnes <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> iestatīšana profila (<xliff:g id="PROFILE_NAME">%2$s</xliff:g> — <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>) pārvaldībai"</string> + <string name="profile_summary" msgid="3167701603666642104">"Lai pārvaldītu profilu (<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>), nepieciešama lietotne <xliff:g id="APP_NAME_0">%1$s</xliff:g>. Kamēr profils (<xliff:g id="PROFILE_NAME2">%5$s</xliff:g>) būs pievienots, lietotnei <xliff:g id="APP_NAME2">%3$s</xliff:g> tiks piešķirta piekļuve šādām atļaujām: <xliff:g id="PERMISSIONS">%4$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Jā"</string> + <string name="consent_no" msgid="1335543792857823917">"Nē, paldies"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml index eb9204ace00d..5cc18c5b793b 100644 --- a/packages/CompanionDeviceManager/res/values-mk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Изберете <xliff:g id="PROFILE_NAME">%1$s</xliff:g> со којшто ќе управува <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"уред"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Поставете ја <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> да управува со <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> е потребна за да управува со <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> ќе добие пристап до <xliff:g id="PERMISSIONS">%4$s</xliff:g> додека <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> е поврзан."</string> + <string name="consent_yes" msgid="4055438216605487056">"Да"</string> + <string name="consent_no" msgid="1335543792857823917">"Не, фала"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ml/strings.xml b/packages/CompanionDeviceManager/res/values-ml/strings.xml index 8e750dee691a..b6734e8d1282 100644 --- a/packages/CompanionDeviceManager/res/values-ml/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ml/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"കമ്പാനിയൻ ഉപകരണ മാനേജർ"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ഉപയോഗിച്ച് മാനേജ് ചെയ്യുന്നതിന് ഒരു <xliff:g id="PROFILE_NAME">%1$s</xliff:g> തിരഞ്ഞെടുക്കുക"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"ഉപകരണം"</string> + <string name="confirmation_title" msgid="4751119145078041732">"നിങ്ങളുടെ <xliff:g id="PROFILE_NAME">%2$s</xliff:g> മാനേജ് ചെയ്യുന്നതിന് <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> സജ്ജീകരിക്കുക - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> എന്ന ആപ്പിന് നിങ്ങളുടെ <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> മാനേജ് ചെയ്യേണ്ടതുണ്ട്. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> കണക്റ്റ് ചെയ്തിരിക്കുമ്പോൾ <xliff:g id="APP_NAME2">%3$s</xliff:g> എന്ന ആപ്പിന് <xliff:g id="PERMISSIONS">%4$s</xliff:g> എന്നിവയിലേക്ക് ആക്സസ് ലഭിക്കും."</string> + <string name="consent_yes" msgid="4055438216605487056">"വേണം"</string> + <string name="consent_no" msgid="1335543792857823917">"വേണ്ട"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-mn/strings.xml b/packages/CompanionDeviceManager/res/values-mn/strings.xml index eb9204ace00d..cd4fdbf572c3 100644 --- a/packages/CompanionDeviceManager/res/values-mn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-mn/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>-н удирдах<xliff:g id="PROFILE_NAME">%1$s</xliff:g>-г сонгоно уу"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"төхөөрөмж"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<xliff:g id="PROFILE_NAME">%2$s</xliff:g>-аа удирдахын тулд <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>-г тохируулна уу - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Таны <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>-г удирдахын тулд <xliff:g id="APP_NAME_0">%1$s</xliff:g> шаардлагатай. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> холбогдсон үед <xliff:g id="APP_NAME2">%3$s</xliff:g> нь <xliff:g id="PERMISSIONS">%4$s</xliff:g>-д хандах эрхтэй болно."</string> + <string name="consent_yes" msgid="4055438216605487056">"Тийм"</string> + <string name="consent_no" msgid="1335543792857823917">"Үгүй, баярлалаа"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml index 4f8f4ee9589f..65bf262d5bcf 100644 --- a/packages/CompanionDeviceManager/res/values-mr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"सहयोगी डिव्हाइस व्यवस्थापक"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> द्वारे व्यवस्थापित करण्यासाठी <xliff:g id="PROFILE_NAME">%1$s</xliff:g> निवडा"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"डिव्हाइस"</string> + <string name="confirmation_title" msgid="4751119145078041732">"तुमची <xliff:g id="PROFILE_NAME">%2$s</xliff:g> व्यवस्थापित करण्यासाठी <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> सेट करा - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"तुमची <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> व्यवस्थापित करण्यासाठी <xliff:g id="APP_NAME_0">%1$s</xliff:g> आवश्यक आहे. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> कनेक्ट केलेली असताना <xliff:g id="APP_NAME2">%3$s</xliff:g> ला <xliff:g id="PERMISSIONS">%4$s</xliff:g> चा अॅक्सेस मिळेल."</string> + <string name="consent_yes" msgid="4055438216605487056">"होय"</string> + <string name="consent_no" msgid="1335543792857823917">"नाही, नको"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml index c170afad5f56..d17041f3b6cb 100644 --- a/packages/CompanionDeviceManager/res/values-ms/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Pengurus Peranti Rakan"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk diurus oleh <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"peranti"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Tetapkan <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> untuk mengurus <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> anda"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> diperlukan untuk mengurus <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> anda. <xliff:g id="APP_NAME2">%3$s</xliff:g> akan mendapat akses kepada <xliff:g id="PERMISSIONS">%4$s</xliff:g> semasa <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> disambungkan."</string> + <string name="consent_yes" msgid="4055438216605487056">"Ya"</string> + <string name="consent_no" msgid="1335543792857823917">"Tidak perlu"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml index 6d0274f5211d..23f165a9259c 100644 --- a/packages/CompanionDeviceManager/res/values-my/strings.xml +++ b/packages/CompanionDeviceManager/res/values-my/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"တွဲဖက်ကိရိယာ မန်နေဂျာ"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> က စီမံခန့်ခွဲရန် <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို ရွေးချယ်ပါ"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"စက်"</string> + <string name="confirmation_title" msgid="4751119145078041732">"သင်၏ <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> ကို စီမံခန့်ခွဲရန် <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ကို သတ်မှတ်ပါ"</string> + <string name="profile_summary" msgid="3167701603666642104">"သင်၏ <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> ကို စီမံခန့်ခွဲရန် <xliff:g id="APP_NAME_0">%1$s</xliff:g> ကို လိုအပ်ပါသည်။ <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> ကို ချိတ်ဆက်ထားစဉ် <xliff:g id="APP_NAME2">%3$s</xliff:g> သည် <xliff:g id="PERMISSIONS">%4$s</xliff:g> ကို ဝင်သုံးခွင့်ရပါမည်။"</string> + <string name="consent_yes" msgid="4055438216605487056">"Yes"</string> + <string name="consent_no" msgid="1335543792857823917">"မလိုပါ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml index eb9204ace00d..090f2a20820c 100644 --- a/packages/CompanionDeviceManager/res/values-nb/strings.xml +++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Velg <xliff:g id="PROFILE_NAME">%1$s</xliff:g> som skal administreres av <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Angi <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> for å administrere <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> kreves for å administrere <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> får tilgang til <xliff:g id="PERMISSIONS">%4$s</xliff:g> når <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> er tilkoblet."</string> + <string name="consent_yes" msgid="4055438216605487056">"Ja"</string> + <string name="consent_no" msgid="1335543792857823917">"Nei takk"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ne/strings.xml b/packages/CompanionDeviceManager/res/values-ne/strings.xml index 99ba62bde50e..e885674fb2db 100644 --- a/packages/CompanionDeviceManager/res/values-ne/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ne/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"सहयोगी यन्त्रको प्रबन्धक"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"आफूले <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> प्रयोग गरी व्यवस्थापन गर्न चाहेको <xliff:g id="PROFILE_NAME">%1$s</xliff:g> चयन गर्नुहोस्"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"यन्त्र"</string> + <string name="confirmation_title" msgid="4751119145078041732">"आफ्नो <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> व्यवस्थापन गर्न <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> तोक्नुहोस्"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> व्यवस्थापन गर्न <xliff:g id="APP_NAME_0">%1$s</xliff:g> इन्स्टल गर्नु पर्ने हुन्छ। <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> कनेक्ट भएका बेला <xliff:g id="APP_NAME2">%3$s</xliff:g> ले <xliff:g id="PERMISSIONS">%4$s</xliff:g> प्रयोग गर्ने अनुमति प्राप्त गर्ने छ।"</string> + <string name="consent_yes" msgid="4055438216605487056">"अँ"</string> + <string name="consent_no" msgid="1335543792857823917">"सहमत छुइनँ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml index 732017a25275..03fae5c5c01c 100644 --- a/packages/CompanionDeviceManager/res/values-or/strings.xml +++ b/packages/CompanionDeviceManager/res/values-or/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"ସହଯୋଗୀ ଡିଭାଇସ୍ ପରିଚାଳକ"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ଦ୍ୱାରା ପରିଚାଳିତ ହେବା ପାଇଁ ଏକ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>କୁ ବାଛନ୍ତୁ"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"ଡିଭାଇସ୍"</string> + <string name="confirmation_title" msgid="4751119145078041732">"ଆପଣଙ୍କ <xliff:g id="PROFILE_NAME">%2$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>କୁ ସେଟ୍ କରନ୍ତୁ - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"ଆପଣଙ୍କ <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ <xliff:g id="APP_NAME_0">%1$s</xliff:g>ର ଆବଶ୍ୟକତା ଅଛି। <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> ସଂଯୁକ୍ତ ହୋଇଥିବା ସମୟରେ <xliff:g id="APP_NAME2">%3$s</xliff:g> <xliff:g id="PERMISSIONS">%4$s</xliff:g>କୁ ଆକ୍ସେସ୍ ପାଇବ।"</string> + <string name="consent_yes" msgid="4055438216605487056">"ହଁ"</string> + <string name="consent_no" msgid="1335543792857823917">"ନା, ଧନ୍ୟବାଦ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-pa/strings.xml b/packages/CompanionDeviceManager/res/values-pa/strings.xml index a61c7593954b..33135b2488c5 100644 --- a/packages/CompanionDeviceManager/res/values-pa/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pa/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"ਸੰਬੰਧੀ ਡੀਵਾਈਸ ਪ੍ਰਬੰਧਕ"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ਵੱਲੋਂ ਪ੍ਰਬੰਧਿਤ ਕੀਤੇ ਜਾਣ ਲਈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ਚੁਣੋ"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"ਡੀਵਾਈਸ"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ਨੂੰ ਤੁਹਾਡਾ <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਸੈੱਟ ਕਰੋ"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ਨੂੰ ਤੁਹਾਡਾ <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਦੀ ਲੋੜ ਹੈ। <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> ਕਨੈਕਟ ਕੀਤੇ ਹੋਣ \'ਤੇ <xliff:g id="APP_NAME2">%3$s</xliff:g> ਨੂੰ <xliff:g id="PERMISSIONS">%4$s</xliff:g> ਤੱਕ ਪਹੁੰਚ ਪ੍ਰਾਪਤ ਹੋ ਜਾਵੇਗੀ।"</string> + <string name="consent_yes" msgid="4055438216605487056">"ਹਾਂ"</string> + <string name="consent_no" msgid="1335543792857823917">"ਨਹੀਂ ਧੰਨਵਾਦ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml index 8d96226e176c..4258e705e7d1 100644 --- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Gerenciador de dispositivos complementar"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Defina o app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> como gerenciador do seu <xliff:g id="PROFILE_NAME">%2$s</xliff:g> (<strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>)"</string> + <string name="profile_summary" msgid="3167701603666642104">"O app <xliff:g id="APP_NAME_0">%1$s</xliff:g> é necessário para gerenciar seu <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> terá acesso a <xliff:g id="PERMISSIONS">%4$s</xliff:g> enquanto o <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> estiver conectado."</string> + <string name="consent_yes" msgid="4055438216605487056">"Sim"</string> + <string name="consent_no" msgid="1335543792857823917">"Agora não"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml index bb845400157d..45b03d604a64 100644 --- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Gestor de dispositivos associados"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerido pela app <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Defina a app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para gerir o seu <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"A app <xliff:g id="APP_NAME_0">%1$s</xliff:g> é necessária para gerir o seu <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. A app <xliff:g id="APP_NAME2">%3$s</xliff:g> terá acesso a <xliff:g id="PERMISSIONS">%4$s</xliff:g> enquanto o <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> estiver associado."</string> + <string name="consent_yes" msgid="4055438216605487056">"Sim"</string> + <string name="consent_no" msgid="1335543792857823917">"Não, obrigado"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml index 8d96226e176c..4258e705e7d1 100644 --- a/packages/CompanionDeviceManager/res/values-pt/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Gerenciador de dispositivos complementar"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Defina o app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> como gerenciador do seu <xliff:g id="PROFILE_NAME">%2$s</xliff:g> (<strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>)"</string> + <string name="profile_summary" msgid="3167701603666642104">"O app <xliff:g id="APP_NAME_0">%1$s</xliff:g> é necessário para gerenciar seu <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> terá acesso a <xliff:g id="PERMISSIONS">%4$s</xliff:g> enquanto o <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> estiver conectado."</string> + <string name="consent_yes" msgid="4055438216605487056">"Sim"</string> + <string name="consent_no" msgid="1335543792857823917">"Agora não"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml index 6446ce060c05..060e9965f2f9 100644 --- a/packages/CompanionDeviceManager/res/values-ro/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Manager de dispozitiv Companion"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Alegeți un profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> pe care să îl gestioneze <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"dispozitiv"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Setați <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> pentru a vă gestiona profilul <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> este necesară pentru a vă gestiona profilul <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> va primi acces la <xliff:g id="PERMISSIONS">%4$s</xliff:g> în timp ce profilul <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> este conectat."</string> + <string name="consent_yes" msgid="4055438216605487056">"Da"</string> + <string name="consent_no" msgid="1335543792857823917">"Nu, mulțumesc"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ru/strings.xml b/packages/CompanionDeviceManager/res/values-ru/strings.xml index 4923f1ff9b0a..7982507c434e 100644 --- a/packages/CompanionDeviceManager/res/values-ru/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ru/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Управление подключенными устройствами"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Выберите устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), которым будет управлять приложение <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Разрешите приложению <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> управлять устройством <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> (<xliff:g id="PROFILE_NAME">%2$s</xliff:g>)"</string> + <string name="profile_summary" msgid="3167701603666642104">"Приложение \"<xliff:g id="APP_NAME_0">%1$s</xliff:g>\" необходимо для управления устройством (<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>). При подключении к устройству (<xliff:g id="PROFILE_NAME2">%5$s</xliff:g>) приложение \"<xliff:g id="APP_NAME2">%3$s</xliff:g>\" получит доступ к следующему: <xliff:g id="PERMISSIONS">%4$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Да"</string> + <string name="consent_no" msgid="1335543792857823917">"Нет"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-si/strings.xml b/packages/CompanionDeviceManager/res/values-si/strings.xml index eb8d87241d90..8bbc1a6827cb 100644 --- a/packages/CompanionDeviceManager/res/values-si/strings.xml +++ b/packages/CompanionDeviceManager/res/values-si/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"සහායක උපාංග කළමනාකරු"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> මගින් කළමනාකරණය කරනු ලැබීමට <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ක් තෝරන්න"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"උපාංගය"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ඔබගේ <xliff:g id="PROFILE_NAME">%2$s</xliff:g> කළමනාකරණය කිරීමට සකසන්න - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ඔබගේ <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> කළමනාකරණය කිරීමට අවශ්යයි. <xliff:g id="APP_NAME2">%3$s</xliff:g> හට <xliff:g id="PERMISSIONS">%4$s</xliff:g> වෙත ප්රවේශය <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> සම්බන්ධිත අතරතුර ලැබෙනු ඇත."</string> + <string name="consent_yes" msgid="4055438216605487056">"ඔව්"</string> + <string name="consent_no" msgid="1335543792857823917">"එපා, ස්තුතියි"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml index bf66ced96bc3..1037a9693da2 100644 --- a/packages/CompanionDeviceManager/res/values-sk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Správca sprievodných zariadení"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Vyberte profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ktorý bude spravovať aplikácia <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"zariadenie"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Nastavte aplikáciu <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>, aby spravovala profil <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Na správu profilu <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> je potrebná aplikácia <xliff:g id="APP_NAME_0">%1$s</xliff:g>. Kým bude profil <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> pripojený, <xliff:g id="APP_NAME2">%3$s</xliff:g> získa prístup k povoleniam <xliff:g id="PERMISSIONS">%4$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Áno"</string> + <string name="consent_no" msgid="1335543792857823917">"Nie, vďaka"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-sl/strings.xml b/packages/CompanionDeviceManager/res/values-sl/strings.xml index 644b96015e06..f2d4c6fb03d0 100644 --- a/packages/CompanionDeviceManager/res/values-sl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sl/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Upravitelj spremljevalnih naprav"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Izbira naprave <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ki jo bo upravljala aplikacija <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"naprava"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Nastavitev aplikacije <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>, ki bo upravljala napravo <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Za upravljanje naprave <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> potrebujete aplikacijo <xliff:g id="APP_NAME_0">%1$s</xliff:g>. Ko je naprava <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> povezana, bo aplikaciji <xliff:g id="APP_NAME2">%3$s</xliff:g> omogočen dostop do teh dovoljenj: <xliff:g id="PERMISSIONS">%4$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Da"</string> + <string name="consent_no" msgid="1335543792857823917">"Ne, hvala"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml index 84812dcf0130..c9336b3b4433 100644 --- a/packages/CompanionDeviceManager/res/values-sq/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Menaxheri i pajisjes shoqëruese"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Zgjidh një profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> që do të menaxhohet nga <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"pajisja"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Cakto <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> që të menaxhojë profilin tënd <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Nevojitet <xliff:g id="APP_NAME_0">%1$s</xliff:g> për të menaxhuar profilin tënd <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> do të marrë qasje në <xliff:g id="PERMISSIONS">%4$s</xliff:g> ndërkohë që është lidhur profili <xliff:g id="PROFILE_NAME2">%5$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Po"</string> + <string name="consent_no" msgid="1335543792857823917">"Jo, faleminderit"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-sr/strings.xml b/packages/CompanionDeviceManager/res/values-sr/strings.xml index 3507af3771ea..5298194bc422 100644 --- a/packages/CompanionDeviceManager/res/values-sr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sr/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Менаџер придруженог уређаја"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Одаберите профил <xliff:g id="PROFILE_NAME">%1$s</xliff:g> којим ће управљати апликација <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"уређај"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Подесите апликацију <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> да управља профилом <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Апликација <xliff:g id="APP_NAME_0">%1$s</xliff:g> је неопходна за управљање профилом <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. <xliff:g id="APP_NAME2">%3$s</xliff:g> ће добити приступ дозволама за <xliff:g id="PERMISSIONS">%4$s</xliff:g> док је <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> повезан."</string> + <string name="consent_yes" msgid="4055438216605487056">"Да"</string> + <string name="consent_no" msgid="1335543792857823917">"Не, хвала"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml index 13e78791670c..ae8ade7ef075 100644 --- a/packages/CompanionDeviceManager/res/values-sw/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Kidhibiti cha Vifaa Visaidizi"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Chagua <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ili idhibitiwe na <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"kifaa"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Weka <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ili udhibiti <xliff:g id="PROFILE_NAME">%2$s</xliff:g> yako - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> linahitajika ili kudhibiti <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> wako. <xliff:g id="APP_NAME2">%3$s</xliff:g> itapata uwezo wa kufikia <xliff:g id="PERMISSIONS">%4$s</xliff:g> wakati <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> imeunganishwa."</string> + <string name="consent_yes" msgid="4055438216605487056">"Ndiyo"</string> + <string name="consent_no" msgid="1335543792857823917">"Hapana"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ta/strings.xml b/packages/CompanionDeviceManager/res/values-ta/strings.xml index 9cc9aa70759f..373ed4543cc9 100644 --- a/packages/CompanionDeviceManager/res/values-ta/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ta/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"கம்பேனியன் சாதன நிர்வாகி"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ஆப்ஸ் நிர்வகிக்கக்கூடிய <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ஐத் தேர்ந்தெடுங்கள்"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"சாதனம்"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> ஐ நிர்வகிக்க <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ஆப்ஸை அமையுங்கள்"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> ஐ நிர்வகிக்க <xliff:g id="APP_NAME_0">%1$s</xliff:g> ஆப்ஸ் வேண்டும். <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> இணைக்கப்பட்டதும் <xliff:g id="PERMISSIONS">%4$s</xliff:g> ஆகியவற்றுக்கான அணுகலை <xliff:g id="APP_NAME2">%3$s</xliff:g> ஆப்ஸ் பெறும்."</string> + <string name="consent_yes" msgid="4055438216605487056">"ஆம்"</string> + <string name="consent_no" msgid="1335543792857823917">"வேண்டாம்"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml index a4dcba6c3cec..f73e7136b5f0 100644 --- a/packages/CompanionDeviceManager/res/values-te/strings.xml +++ b/packages/CompanionDeviceManager/res/values-te/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"సహచర పరికర మేనేజర్"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ద్వారా మేనేజ్ చేయబడటానికి ఒక <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ను ఎంచుకోండి"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"పరికరం"</string> + <string name="confirmation_title" msgid="4751119145078041732">"మీ <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>ను మేనేజ్ చేయడానికి <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>ను సెటప్ చేయండి"</string> + <string name="profile_summary" msgid="3167701603666642104">"మీ <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>ను మేనేజ్ చేయడానికి <xliff:g id="APP_NAME_0">%1$s</xliff:g> అవసరం ఉంది. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> కనెక్ట్ అయినప్పుడు <xliff:g id="APP_NAME2">%3$s</xliff:g>, <xliff:g id="PERMISSIONS">%4$s</xliff:g>కు యాక్సెస్ను పొందుతుంది."</string> + <string name="consent_yes" msgid="4055438216605487056">"అవును"</string> + <string name="consent_no" msgid="1335543792857823917">"వద్దు, ధన్యవాదాలు"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-th/strings.xml b/packages/CompanionDeviceManager/res/values-th/strings.xml index eb9204ace00d..8c1848a1e582 100644 --- a/packages/CompanionDeviceManager/res/values-th/strings.xml +++ b/packages/CompanionDeviceManager/res/values-th/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"เลือก<xliff:g id="PROFILE_NAME">%1$s</xliff:g>ที่จะให้มีการจัดการโดย <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"อุปกรณ์"</string> + <string name="confirmation_title" msgid="4751119145078041732">"ตั้งค่า <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ให้จัดการ<xliff:g id="PROFILE_NAME">%2$s</xliff:g>ของคุณ - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"ต้องใช้ <xliff:g id="APP_NAME_0">%1$s</xliff:g> ในการจัดการ<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> <xliff:g id="APP_NAME2">%3$s</xliff:g> จะได้รับสิทธิ์เข้าถึง<xliff:g id="PERMISSIONS">%4$s</xliff:g>ในขณะที่มีการเชื่อมต่อ<xliff:g id="PROFILE_NAME2">%5$s</xliff:g>"</string> + <string name="consent_yes" msgid="4055438216605487056">"ใช่"</string> + <string name="consent_no" msgid="1335543792857823917">"ไม่เป็นไร"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-tl/strings.xml b/packages/CompanionDeviceManager/res/values-tl/strings.xml index 1f0d78e28c41..8fcc3d27d5d5 100644 --- a/packages/CompanionDeviceManager/res/values-tl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-tl/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Kasamang Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Pumili ng <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para pamahalaan ng <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"device"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Itakda ang <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para pamahalaan ang iyong <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Kailangan ang <xliff:g id="APP_NAME_0">%1$s</xliff:g> para pamahalaan ang iyong <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>. Magkakaroon ng access ang <xliff:g id="APP_NAME2">%3$s</xliff:g> sa <xliff:g id="PERMISSIONS">%4$s</xliff:g> habang nakakonekta ang <xliff:g id="PROFILE_NAME2">%5$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Oo"</string> + <string name="consent_no" msgid="1335543792857823917">"Huwag na lang"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml index eb9204ace00d..255eca50d2b2 100644 --- a/packages/CompanionDeviceManager/res/values-tr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> tarafından yönetilecek bir <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> uygulamasını, <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> cihazınızı yönetecek şekilde ayarlayın"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g>, <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> yönetimi için gereklidir. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> bağlıyken <xliff:g id="APP_NAME2">%3$s</xliff:g>, şunlara erişebilecek: <xliff:g id="PERMISSIONS">%4$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Evet"</string> + <string name="consent_no" msgid="1335543792857823917">"Hayır, teşekkürler"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml index 46de2cdcdcb2..b5827f2c78ad 100644 --- a/packages/CompanionDeviceManager/res/values-uk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Диспетчер супутніх пристроїв"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Виберіть <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, яким керуватиме додаток <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"пристрій"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Налаштуйте додаток <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>, щоб керувати своїм пристроєм <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> (<xliff:g id="PROFILE_NAME">%2$s</xliff:g>)"</string> + <string name="profile_summary" msgid="3167701603666642104">"Щоб керувати своїм пристроєм (<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>), вам потрібен додаток <xliff:g id="APP_NAME_0">%1$s</xliff:g>. Коли <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> буде підключено, додаток <xliff:g id="APP_NAME2">%3$s</xliff:g> отримає такі дозволи на доступ: <xliff:g id="PERMISSIONS">%4$s</xliff:g>."</string> + <string name="consent_yes" msgid="4055438216605487056">"Так"</string> + <string name="consent_no" msgid="1335543792857823917">"Ні, дякую"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml index d2ce1fbe4f6b..2bbffdcb7c2d 100644 --- a/packages/CompanionDeviceManager/res/values-ur/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"ساتھی آلہ مینیجر"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> کے ذریعے نظم کئے جانے کیلئے <xliff:g id="PROFILE_NAME">%1$s</xliff:g> کو منتخب کریں"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"آلہ"</string> + <string name="confirmation_title" msgid="4751119145078041732">"اپنے <xliff:g id="PROFILE_NAME">%2$s</xliff:g> کا نظم کرنے کے لیے <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> کو سیٹ کریں - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"آپ کے <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> کا نظم کرنے کے لیے <xliff:g id="APP_NAME_0">%1$s</xliff:g> کی ضرورت ہے۔ <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> کے منسلک ہونے پر <xliff:g id="APP_NAME2">%3$s</xliff:g> <xliff:g id="PERMISSIONS">%4$s</xliff:g> تک رسائی حاصل کرے گا۔"</string> + <string name="consent_yes" msgid="4055438216605487056">"ہاں"</string> + <string name="consent_no" msgid="1335543792857823917">"نہیں شکریہ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml index eb9204ace00d..96c49f2fed3c 100644 --- a/packages/CompanionDeviceManager/res/values-uz/strings.xml +++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> boshqaradigan <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qurilmasini tanlang"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"qurilma"</string> + <string name="confirmation_title" msgid="4751119145078041732">"<xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> qurilmalarini boshqarish uchun <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ilovasini sozlang"</string> + <string name="profile_summary" msgid="3167701603666642104">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ilovasi <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> qurilmasini boshqarish uchun kerak. <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> qurilmasiga <xliff:g id="APP_NAME2">%3$s</xliff:g> ilovasi ulansa, u quyidagi ruxsatlarni oladi: <xliff:g id="PERMISSIONS">%4$s</xliff:g>"</string> + <string name="consent_yes" msgid="4055438216605487056">"Ha"</string> + <string name="consent_no" msgid="1335543792857823917">"Kerak emas"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-vi/strings.xml b/packages/CompanionDeviceManager/res/values-vi/strings.xml index eb9204ace00d..d67db4141fe9 100644 --- a/packages/CompanionDeviceManager/res/values-vi/strings.xml +++ b/packages/CompanionDeviceManager/res/values-vi/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Chọn một <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sẽ do <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> quản lý"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"thiết bị"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Đặt <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> để quản lý <xliff:g id="PROFILE_NAME">%2$s</xliff:g> của bạn – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"Cần có <xliff:g id="APP_NAME_0">%1$s</xliff:g> để quản lý <xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> của bạn. <xliff:g id="APP_NAME2">%3$s</xliff:g> sẽ có quyền truy cập vào <xliff:g id="PERMISSIONS">%4$s</xliff:g> trong khi <xliff:g id="PROFILE_NAME2">%5$s</xliff:g> được kết nối."</string> + <string name="consent_yes" msgid="4055438216605487056">"Có"</string> + <string name="consent_no" msgid="1335543792857823917">"Không, cảm ơn"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml index 64efeacb60e1..a1abd98dfa1b 100644 --- a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml +++ b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"配套设备管理器"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"选择要由<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"设备"</string> + <string name="confirmation_title" msgid="4751119145078041732">"设为由<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>管理您的<xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"若要管理<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>,您需要使用<xliff:g id="APP_NAME_0">%1$s</xliff:g>。在已连接<xliff:g id="PROFILE_NAME2">%5$s</xliff:g>的情况下,<xliff:g id="APP_NAME2">%3$s</xliff:g>将能够访问<xliff:g id="PERMISSIONS">%4$s</xliff:g>。"</string> + <string name="consent_yes" msgid="4055438216605487056">"好"</string> + <string name="consent_no" msgid="1335543792857823917">"不用了"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml index ecacf3a316a0..57d2173a2d2a 100644 --- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml +++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"隨附裝置管理員"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"選擇由 <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> 管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string> + <string name="confirmation_title" msgid="4751119145078041732">"設定 <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> 來管理您的 <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"必須使用 <xliff:g id="APP_NAME_0">%1$s</xliff:g> 來管理您的<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>。連結<xliff:g id="PROFILE_NAME2">%5$s</xliff:g>後,<xliff:g id="APP_NAME2">%3$s</xliff:g> 將可以存取<xliff:g id="PERMISSIONS">%4$s</xliff:g>。"</string> + <string name="consent_yes" msgid="4055438216605487056">"是"</string> + <string name="consent_no" msgid="1335543792857823917">"不用了,謝謝"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml index ecacf3a316a0..c9a2fd85edcc 100644 --- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml +++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"隨附裝置管理員"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"選擇要讓「<xliff:g id="APP_NAME">%2$s</xliff:g>」<strong></strong>管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string> + <string name="confirmation_title" msgid="4751119145078041732">"授權讓「<xliff:g id="APP_NAME">%1$s</xliff:g>」<strong></strong>管理你的<xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"如要管理你的<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g>,必須使用「<xliff:g id="APP_NAME_0">%1$s</xliff:g>」。與<xliff:g id="PROFILE_NAME2">%5$s</xliff:g>連線時,「<xliff:g id="APP_NAME2">%3$s</xliff:g>」將有權存取你的<xliff:g id="PERMISSIONS">%4$s</xliff:g>。"</string> + <string name="consent_yes" msgid="4055438216605487056">"是"</string> + <string name="consent_no" msgid="1335543792857823917">"不用了,謝謝"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-zu/strings.xml b/packages/CompanionDeviceManager/res/values-zu/strings.xml index 74f7a0ea2ba8..c811037bd24c 100644 --- a/packages/CompanionDeviceManager/res/values-zu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-zu/strings.xml @@ -17,16 +17,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"Isiphathi sedivayisi esihambisanayo"</string> - <!-- no translation found for chooser_title (2262294130493605839) --> - <skip /> - <!-- no translation found for profile_name_generic (6851028682723034988) --> - <skip /> - <!-- no translation found for confirmation_title (4751119145078041732) --> - <skip /> - <!-- no translation found for profile_summary (3167701603666642104) --> - <skip /> - <!-- no translation found for consent_yes (4055438216605487056) --> - <skip /> - <!-- no translation found for consent_no (1335543792857823917) --> - <skip /> + <string name="chooser_title" msgid="2262294130493605839">"Khetha i-<xliff:g id="PROFILE_NAME">%1$s</xliff:g> ezophathwa yi-<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> + <string name="profile_name_generic" msgid="6851028682723034988">"idivayisi"</string> + <string name="confirmation_title" msgid="4751119145078041732">"Setha i-<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ukuba iphathe i-<xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> + <string name="profile_summary" msgid="3167701603666642104">"I-<xliff:g id="APP_NAME_0">%1$s</xliff:g> iyadingeka ukuphatha i-<xliff:g id="PROFILE_NAME_1">%2$s</xliff:g> yakho. I-<xliff:g id="APP_NAME2">%3$s</xliff:g> izothola ukufinyelela ku-<xliff:g id="PERMISSIONS">%4$s</xliff:g> kuyilapho i-<xliff:g id="PROFILE_NAME2">%5$s</xliff:g> ixhunyiwe."</string> + <string name="consent_yes" msgid="4055438216605487056">"Yebo"</string> + <string name="consent_no" msgid="1335543792857823917">"Cha ngiyabonga"</string> </resources> diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp index a26f715280a1..c8f3bd3666e4 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 @@ cc_defaults { srcs: [ "jni/com_android_server_TestNetworkService.cpp", "jni/com_android_server_connectivity_Vpn.cpp", + "jni/onload.cpp", ], shared_libs: [ "libbase", @@ -35,27 +36,11 @@ cc_defaults { // 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 @@ java_library { ], apex_available: [ "//apex_available:platform", + "com.android.tethering", ], } diff --git a/packages/FusedLocation/AndroidManifest.xml b/packages/FusedLocation/AndroidManifest.xml index bad0497c487d..12dc170413de 100644 --- a/packages/FusedLocation/AndroidManifest.xml +++ b/packages/FusedLocation/AndroidManifest.xml @@ -40,7 +40,7 @@ LocationManagerService will bind to the service with the highest version. --> <service android:name="com.android.location.fused.FusedLocationService" - android:exported="true" + android:exported="false" android:permission="android.permission.WRITE_SECURE_SETTINGS"> <intent-filter> <action android:name="com.android.location.service.FusedLocationProvider" /> diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java index 6827d6eb0180..cb55c727809a 100644 --- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java +++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java @@ -20,6 +20,8 @@ import static android.content.Intent.ACTION_USER_SWITCHED; import static android.location.LocationManager.GPS_PROVIDER; import static android.location.LocationManager.NETWORK_PROVIDER; import static android.location.LocationRequest.QUALITY_LOW_POWER; +import static android.location.provider.ProviderProperties.ACCURACY_FINE; +import static android.location.provider.ProviderProperties.POWER_USAGE_LOW; import static com.android.location.provider.ProviderRequestUnbundled.INTERVAL_DISABLED; @@ -28,113 +30,57 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.location.Criteria; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationRequest; -import android.os.WorkSource; +import android.location.provider.LocationProviderBase; +import android.location.provider.ProviderProperties; +import android.location.provider.ProviderRequest; +import android.os.Bundle; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.location.ProviderRequest; -import com.android.location.provider.LocationProviderBase; -import com.android.location.provider.ProviderPropertiesUnbundled; -import com.android.location.provider.ProviderRequestUnbundled; import java.io.PrintWriter; +import java.util.Objects; /** Basic fused location provider implementation. */ public class FusedLocationProvider extends LocationProviderBase { private static final String TAG = "FusedLocationProvider"; - private static final ProviderPropertiesUnbundled PROPERTIES = - ProviderPropertiesUnbundled.create( - /* requiresNetwork = */ false, - /* requiresSatellite = */ false, - /* requiresCell = */ false, - /* hasMonetaryCost = */ false, - /* supportsAltitude = */ true, - /* supportsSpeed = */ true, - /* supportsBearing = */ true, - Criteria.POWER_LOW, - Criteria.ACCURACY_FINE - ); + private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder() + .setHasAltitudeSupport(true) + .setHasSpeedSupport(true) + .setHasBearingSupport(true) + .setPowerUsage(POWER_USAGE_LOW) + .setAccuracy(ACCURACY_FINE) + .build(); private static final long MAX_LOCATION_COMPARISON_NS = 11 * 1000000000L; // 11 seconds - final Object mLock = new Object(); + private final Object mLock = new Object(); private final Context mContext; private final LocationManager mLocationManager; - private final LocationListener mGpsListener; - private final LocationListener mNetworkListener; + private final ChildLocationListener mGpsListener; + private final ChildLocationListener mNetworkListener; private final BroadcastReceiver mUserChangeReceiver; @GuardedBy("mLock") - ProviderRequestUnbundled mRequest; - @GuardedBy("mLock") - private WorkSource mWorkSource; - @GuardedBy("mLock") - private long mGpsInterval; - @GuardedBy("mLock") - private long mNetworkInterval; + private ProviderRequest mRequest; @GuardedBy("mLock") - @Nullable private Location mFusedLocation; - @GuardedBy("mLock") - @Nullable Location mGpsLocation; - @GuardedBy("mLock") - @Nullable Location mNetworkLocation; + private @Nullable Location mFusedLocation; public FusedLocationProvider(Context context) { super(context, TAG, PROPERTIES); mContext = context; - mLocationManager = context.getSystemService(LocationManager.class); - - mGpsListener = new LocationListener() { - @Override - public void onLocationChanged(Location location) { - synchronized (mLock) { - mGpsLocation = location; - reportBestLocationLocked(); - } - } - - @Override - public void onProviderDisabled(String provider) { - synchronized (mLock) { - // if satisfying a bypass request, don't clear anything - if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) { - return; - } - - mGpsLocation = null; - } - } - }; - - mNetworkListener = new LocationListener() { - @Override - public void onLocationChanged(Location location) { - synchronized (mLock) { - mNetworkLocation = location; - reportBestLocationLocked(); - } - } + mLocationManager = Objects.requireNonNull(context.getSystemService(LocationManager.class)); - @Override - public void onProviderDisabled(String provider) { - synchronized (mLock) { - // if satisfying a bypass request, don't clear anything - if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) { - return; - } - - mNetworkLocation = null; - } - } - }; + mGpsListener = new ChildLocationListener(GPS_PROVIDER); + mNetworkListener = new ChildLocationListener(NETWORK_PROVIDER); mUserChangeReceiver = new BroadcastReceiver() { @Override @@ -147,10 +93,7 @@ public class FusedLocationProvider extends LocationProviderBase { } }; - mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST); - mWorkSource = new WorkSource(); - mGpsInterval = INTERVAL_DISABLED; - mNetworkInterval = INTERVAL_DISABLED; + mRequest = ProviderRequest.EMPTY_REQUEST; } void start() { @@ -161,57 +104,53 @@ public class FusedLocationProvider extends LocationProviderBase { mContext.unregisterReceiver(mUserChangeReceiver); synchronized (mLock) { - mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST); + mRequest = ProviderRequest.EMPTY_REQUEST; updateRequirementsLocked(); } } @Override - public void onSetRequest(ProviderRequestUnbundled request, WorkSource workSource) { + public void onSetRequest(ProviderRequest request) { synchronized (mLock) { mRequest = request; - mWorkSource = workSource; updateRequirementsLocked(); } } - @GuardedBy("mLock") - private void updateRequirementsLocked() { - long gpsInterval = mRequest.getQuality() < QUALITY_LOW_POWER ? mRequest.getInterval() - : INTERVAL_DISABLED; - long networkInterval = mRequest.getInterval(); + @Override + public void onFlush(OnFlushCompleteCallback callback) { + OnFlushCompleteCallback wrapper = new OnFlushCompleteCallback() { + private int mFlushCount = 2; - if (gpsInterval != mGpsInterval) { - resetProviderRequestLocked(GPS_PROVIDER, mGpsInterval, gpsInterval, mGpsListener); - mGpsInterval = gpsInterval; - } - if (networkInterval != mNetworkInterval) { - resetProviderRequestLocked(NETWORK_PROVIDER, mNetworkInterval, networkInterval, - mNetworkListener); - mNetworkInterval = networkInterval; - } + @Override + public void onFlushComplete() { + if (--mFlushCount == 0) { + callback.onFlushComplete(); + } + } + }; + + mGpsListener.flush(wrapper); + mNetworkListener.flush(wrapper); } + @Override + public void onSendExtraCommand(String command, @Nullable Bundle extras) {} + @GuardedBy("mLock") - private void resetProviderRequestLocked(String provider, long oldInterval, long newInterval, - LocationListener listener) { - if (oldInterval != INTERVAL_DISABLED && newInterval == INTERVAL_DISABLED) { - mLocationManager.removeUpdates(listener); - } - if (newInterval != INTERVAL_DISABLED) { - LocationRequest request = new LocationRequest.Builder(newInterval) - .setQuality(mRequest.getQuality()) - .setLocationSettingsIgnored(mRequest.isLocationSettingsIgnored()) - .setWorkSource(mWorkSource) - .build(); - mLocationManager.requestLocationUpdates(provider, request, mContext.getMainExecutor(), - listener); - } + private void updateRequirementsLocked() { + long gpsInterval = mRequest.getQuality() < QUALITY_LOW_POWER ? mRequest.getIntervalMillis() + : INTERVAL_DISABLED; + long networkInterval = mRequest.getIntervalMillis(); + + mGpsListener.resetProviderRequest(gpsInterval); + mNetworkListener.resetProviderRequest(networkInterval); } @GuardedBy("mLock") void reportBestLocationLocked() { - Location bestLocation = chooseBestLocation(mGpsLocation, mNetworkLocation); + Location bestLocation = chooseBestLocation(mGpsListener.getLocation(), + mNetworkListener.getLocation()); if (bestLocation == mFusedLocation) { return; } @@ -228,25 +167,25 @@ public class FusedLocationProvider extends LocationProviderBase { // clear cached locations when the user changes to prevent leaking user information synchronized (mLock) { mFusedLocation = null; - mGpsLocation = null; - mNetworkLocation = null; + mGpsListener.clearLocation(); + mNetworkListener.clearLocation(); } } void dump(PrintWriter writer) { synchronized (mLock) { writer.println("request: " + mRequest); - if (mGpsInterval != INTERVAL_DISABLED) { - writer.println(" gps interval: " + mGpsInterval); + if (mGpsListener.getInterval() != INTERVAL_DISABLED) { + writer.println(" gps interval: " + mGpsListener.getInterval()); } - if (mNetworkInterval != INTERVAL_DISABLED) { - writer.println(" network interval: " + mNetworkInterval); + if (mNetworkListener.getInterval() != INTERVAL_DISABLED) { + writer.println(" network interval: " + mNetworkListener.getInterval()); } - if (mGpsLocation != null) { - writer.println(" last gps location: " + mGpsLocation); + if (mGpsListener.getLocation() != null) { + writer.println(" last gps location: " + mGpsListener.getLocation()); } - if (mNetworkLocation != null) { - writer.println(" last network location: " + mNetworkLocation); + if (mNetworkListener.getLocation() != null) { + writer.println(" last network location: " + mNetworkListener.getLocation()); } } } @@ -279,4 +218,104 @@ public class FusedLocationProvider extends LocationProviderBase { } return locationA.getAccuracy() < locationB.getAccuracy() ? locationA : locationB; } + + private class ChildLocationListener implements LocationListener { + + private final String mProvider; + private final SparseArray<OnFlushCompleteCallback> mPendingFlushes; + + @GuardedBy("mLock") + private int mNextFlushCode = 0; + @GuardedBy("mLock") + private @Nullable Location mLocation = null; + @GuardedBy("mLock") + private long mInterval = INTERVAL_DISABLED; + + ChildLocationListener(String provider) { + mProvider = provider; + mPendingFlushes = new SparseArray<>(); + } + + @Nullable Location getLocation() { + synchronized (mLock) { + return mLocation; + } + } + + long getInterval() { + synchronized (mLock) { + return mInterval; + } + } + + void clearLocation() { + synchronized (mLock) { + mLocation = null; + } + } + + private void resetProviderRequest(long newInterval) { + synchronized (mLock) { + if (newInterval == mInterval) { + return; + } + + if (mInterval != INTERVAL_DISABLED && newInterval == INTERVAL_DISABLED) { + mLocationManager.removeUpdates(this); + } + + mInterval = newInterval; + + if (mInterval != INTERVAL_DISABLED) { + LocationRequest request = new LocationRequest.Builder(mInterval) + .setMaxUpdateDelayMillis(mRequest.getMaxUpdateDelayMillis()) + .setQuality(mRequest.getQuality()) + .setLowPower(mRequest.isLowPower()) + .setLocationSettingsIgnored(mRequest.isLocationSettingsIgnored()) + .setWorkSource(mRequest.getWorkSource()) + .build(); + mLocationManager.requestLocationUpdates(mProvider, request, + mContext.getMainExecutor(), this); + } + } + } + + void flush(OnFlushCompleteCallback callback) { + synchronized (mLock) { + int requestCode = mNextFlushCode++; + mPendingFlushes.put(requestCode, callback); + mLocationManager.requestFlush(mProvider, this, requestCode); + } + } + + @Override + public void onLocationChanged(Location location) { + synchronized (mLock) { + mLocation = location; + reportBestLocationLocked(); + } + } + + @Override + public void onProviderDisabled(String provider) { + synchronized (mLock) { + // if satisfying a bypass request, don't clear anything + if (mRequest.isActive() && mRequest.isLocationSettingsIgnored()) { + return; + } + + mLocation = null; + } + } + + @Override + public void onFlushComplete(int requestCode) { + synchronized (mLock) { + OnFlushCompleteCallback callback = mPendingFlushes.removeReturnOld(requestCode); + if (callback != null) { + callback.onFlushComplete(); + } + } + } + } } diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java index 2bda530b83d4..d47231147a38 100644 --- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java +++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.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. @@ -27,18 +27,18 @@ import android.location.Location; import android.location.LocationManager; import android.location.LocationRequest; import android.location.LocationResult; -import android.location.ProviderProperties; +import android.location.provider.ILocationProvider; +import android.location.provider.ILocationProviderManager; +import android.location.provider.ProviderProperties; +import android.location.provider.ProviderRequest; import android.os.ParcelFileDescriptor; import android.os.SystemClock; -import android.os.WorkSource; import android.util.Log; import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.location.ILocationProvider; -import com.android.internal.location.ILocationProviderManager; -import com.android.internal.location.ProviderRequest; import com.android.location.fused.FusedLocationProvider; import org.junit.After; @@ -71,7 +71,7 @@ public class FusedLocationServiceTest { long seed = System.currentTimeMillis(); Log.i(TAG, "location seed: " + seed); - Context context = InstrumentationRegistry.getTargetContext(); + Context context = ApplicationProvider.getApplicationContext(); mRandom = new Random(seed); mLocationManager = context.getSystemService(LocationManager.class); @@ -120,8 +120,7 @@ public class FusedLocationServiceTest { mProvider.setRequest( new ProviderRequest.Builder() .setIntervalMillis(1000) - .build(), - new WorkSource()); + .build()); Location location = createLocation(NETWORK_PROVIDER, mRandom); mLocationManager.setTestProviderLocation(NETWORK_PROVIDER, location); @@ -135,8 +134,7 @@ public class FusedLocationServiceTest { new ProviderRequest.Builder() .setQuality(LocationRequest.QUALITY_HIGH_ACCURACY) .setIntervalMillis(1000) - .build(), - new WorkSource()); + .build()); Location location = createLocation(GPS_PROVIDER, mRandom); mLocationManager.setTestProviderLocation(GPS_PROVIDER, location); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index 1f3ee6d27685..b0c316908a10 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -347,17 +347,19 @@ public class PackageInstallerActivity extends AlertActivity { if (!wasSetUp) { return; } - - // load dummy layout with OK button disabled until we override this layout in - // startInstallConfirm - bindUi(); - checkIfAllowedAndInitiateInstall(); } @Override protected void onResume() { super.onResume(); + if (mAppSnippet != null) { + // load dummy layout with OK button disabled until we override this layout in + // startInstallConfirm + bindUi(); + checkIfAllowedAndInitiateInstall(); + } + if (mOk != null) { mOk.setEnabled(mEnableOk); } diff --git a/packages/PrintSpooler/res/values-or/strings.xml b/packages/PrintSpooler/res/values-or/strings.xml index 7000b95b35d6..15cecd6f46cc 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/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java b/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java index 1c0e718a17b2..4d454941af7c 100644 --- a/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java +++ b/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java @@ -19,9 +19,11 @@ package com.android.settingslib.emergencynumber; import static android.telephony.emergency.EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE; import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; -import android.provider.Settings; +import android.net.Uri; +import android.os.Bundle; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.emergency.EmergencyNumber; @@ -40,7 +42,16 @@ import java.util.Map; */ public class EmergencyNumberUtils { private static final String TAG = "EmergencyNumberUtils"; - private static final String EMERGENCY_GESTURE_CALL_NUMBER = "emergency_gesture_call_number"; + + public static final Uri EMERGENCY_NUMBER_OVERRIDE_AUTHORITY = new Uri.Builder().scheme( + ContentResolver.SCHEME_CONTENT) + .authority("com.android.emergency.numbers") + .build(); + public static final String METHOD_NAME_GET_EMERGENCY_NUMBER_OVERRIDE = + "GET_EMERGENCY_NUMBER_OVERRIDE"; + public static final String METHOD_NAME_SET_EMERGENCY_NUMBER_OVERRIDE = + "SET_EMERGENCY_NUMBER_OVERRIDE"; + public static final String EMERGENCY_GESTURE_CALL_NUMBER = "emergency_gesture_call_number"; @VisibleForTesting static final String FALL_BACK_NUMBER = "112"; @@ -77,12 +88,28 @@ public class EmergencyNumberUtils { * #getDefaultPoliceNumber()}). */ public String getPoliceNumber() { - final String userProvidedNumber = Settings.Secure.getString(mContext.getContentResolver(), - EMERGENCY_GESTURE_CALL_NUMBER); + final String userProvidedNumber = getEmergencyNumberOverride(); return TextUtils.isEmpty(userProvidedNumber) ? getDefaultPoliceNumber() : userProvidedNumber; } + /** + * Sets device-local emergency number override + */ + public void setEmergencyNumberOverride(String number) { + final Bundle bundle = new Bundle(); + bundle.putString(EMERGENCY_GESTURE_CALL_NUMBER, number); + mContext.getContentResolver().call(EMERGENCY_NUMBER_OVERRIDE_AUTHORITY, + METHOD_NAME_SET_EMERGENCY_NUMBER_OVERRIDE, null /* args */, bundle); + } + + private String getEmergencyNumberOverride() { + final Bundle bundle = mContext.getContentResolver().call( + EMERGENCY_NUMBER_OVERRIDE_AUTHORITY, + METHOD_NAME_GET_EMERGENCY_NUMBER_OVERRIDE, null /* args */, null /* bundle */); + return bundle == null ? null : bundle.getString(EMERGENCY_GESTURE_CALL_NUMBER); + } + private List<EmergencyNumber> getPromotedEmergencyNumbers(int categories) { // TODO(b/171542607): Use platform API when its bug is fixed. Map<Integer, List<EmergencyNumber>> allLists = filterEmergencyNumbersByCategories( diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml index a9e9f8cb9ca5..b1553e9f463f 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml @@ -15,10 +15,10 @@ limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" android:layout_width="match_parent" - android:background="@android:color/transparent" android:orientation="vertical"> <LinearLayout @@ -26,7 +26,7 @@ android:minHeight="@dimen/min_switch_bar_height" android:layout_height="wrap_content" android:layout_width="match_parent" - android:background="@android:color/transparent" + android:background="?android:attr/colorBackground" android:paddingLeft="@dimen/switchbar_margin_start" android:paddingRight="@dimen/switchbar_margin_end"> diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml b/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml index b4013988fc84..ac8ab14738e9 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml @@ -17,7 +17,6 @@ <resources> - <color name="background_color">@android:color/transparent</color> <color name="title_text_color">@*android:color/primary_text_light</color> <color name="thumb_off">#BFFFFFFF</color> <color name="track_on">@*android:color/accent_device_default_dark</color> diff --git a/packages/SettingsLib/res/drawable/ic_carrier_wifi.xml b/packages/SettingsLib/res/drawable/ic_carrier_wifi.xml new file mode 100644 index 000000000000..ed9d85e3cbe4 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_carrier_wifi.xml @@ -0,0 +1,30 @@ +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="38dp" + android:height="24dp" + android:viewportWidth="38.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M9.45,14.48h1.8c-0.05,0.98 -0.24,1.82 -0.6,2.53c-0.35,0.7 -0.85,1.24 -1.51,1.62c-0.66,0.38 -1.48,0.57 -2.47,0.57c-0.71,0 -1.35,-0.14 -1.92,-0.42c-0.57,-0.28 -1.06,-0.68 -1.47,-1.2c-0.4,-0.53 -0.71,-1.16 -0.93,-1.89c-0.21,-0.74 -0.32,-1.56 -0.32,-2.48v-2.63c0,-0.91 0.11,-1.74 0.32,-2.47c0.22,-0.74 0.54,-1.36 0.95,-1.88C3.71,5.69 4.21,5.29 4.8,5.01c0.6,-0.28 1.28,-0.42 2.03,-0.42c0.92,0 1.71,0.19 2.34,0.56c0.64,0.36 1.14,0.9 1.48,1.61c0.35,0.7 0.55,1.57 0.59,2.59h-1.8C9.41,8.59 9.29,7.98 9.1,7.52C8.91,7.04 8.63,6.69 8.26,6.47C7.9,6.24 7.42,6.13 6.84,6.13c-0.52,0 -0.97,0.1 -1.36,0.31C5.1,6.65 4.79,6.95 4.54,7.34C4.3,7.72 4.12,8.19 3.99,8.74c-0.12,0.54 -0.18,1.15 -0.18,1.82v2.65c0,0.62 0.05,1.21 0.15,1.75c0.1,0.54 0.27,1.02 0.49,1.43c0.23,0.4 0.52,0.72 0.89,0.95c0.36,0.23 0.81,0.34 1.33,0.34c0.66,0 1.18,-0.11 1.56,-0.32c0.38,-0.21 0.67,-0.56 0.85,-1.03C9.27,15.85 9.39,15.23 9.45,14.48z"/> + <path + android:fillColor="#FF000000" + android:pathData="M24.87,4.78l-1.74,9.72l-0.2,1.64l-0.29,-1.44l-2.21,-9.92l-0.2,0l-1.06,0l-0.22,0l-2.28,9.92l-0.28,1.4l-0.18,-1.59l-1.78,-9.73l-1.79,0l2.83,14.22l0.34,0l0.92,0l0.35,0l2.48,-10.36l0.14,-0.84l0.15,0.84l2.36,10.36l0.36,0l0.92,0l0.34,0l2.82,-14.22z"/> + <path + android:fillColor="#FF000000" + android:pathData="M35.72,6.32l0,-1.54l-5.61,0l-0.34,0l-1.45,0l0,14.22l1.79,0l0,-6.28l4.8,0l0,-1.54l-4.8,0l0,-4.86z"/> +</vector> diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index c2b26bce6e1a..d76a8a177449 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skep tans nuwe gebruiker …"</string> <string name="user_nickname" msgid="262624187455825083">"Bynaam"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Voeg gas by"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Verwyder gas"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Gas"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Neem \'n foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Kies \'n prent"</string> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 65ce6e0200f4..85b5bc2a574e 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"አዲስ ተጠቃሚ በመፍጠር ላይ…"</string> <string name="user_nickname" msgid="262624187455825083">"ቅጽል ስም"</string> <string name="guest_new_guest" msgid="3482026122932643557">"እንግዳን አክል"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"እንግዳን አስወግድ"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"እንግዳ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ፎቶ አንሳ"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ምስል ይምረጡ"</string> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 3ea96c7dbf25..f2605597c803 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> إلى أن يتم شحن الجهاز بالكامل"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> إلى أن يتم شحن الجهاز بالكامل"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - التحسين لسلامة البطارية"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - التحسين للحفاظ على سلامة البطارية"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"غير معروف"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"جارٍ الشحن"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"جارٍ الشحن سريعًا"</string> @@ -557,7 +557,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"جارٍ إنشاء مستخدم جديد…"</string> <string name="user_nickname" msgid="262624187455825083">"اللقب"</string> <string name="guest_new_guest" msgid="3482026122932643557">"إضافة ضيف"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"إزالة جلسة الضيف"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"ضيف"</string> <string name="user_image_take_photo" msgid="467512954561638530">"التقاط صورة"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"اختيار صورة"</string> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index a2596878e33f..32be72d32a12 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যৱহাৰকাৰী সৃষ্টি কৰি থকা হৈছে…"</string> <string name="user_nickname" msgid="262624187455825083">"উপনাম"</string> <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ কৰক"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি আঁতৰাওক"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"অতিথিৰ ছেশ্বন সমাপ্ত কৰক"</string> <string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string> <string name="user_image_take_photo" msgid="467512954561638530">"এখন ফট’ তোলক"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"এখন প্ৰতিচ্ছবি বাছনি কৰক"</string> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index 2cbd681cef45..88e99ce3c9fb 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni istifadəçi yaradılır…"</string> <string name="user_nickname" msgid="262624187455825083">"Ləqəb"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Qonaq əlavə edin"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Qonağı silin"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Qonaq"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Foto çəkin"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Şəkil seçin"</string> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index 4d8e348cb746..4a820ea520d8 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Napuniće se za <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napuniće se za <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimizuje se radi stanja baterije"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimizuje se radi boljeg stanja baterije"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Puni se"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo se puni"</string> @@ -554,7 +554,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Pravi se novi korisnik…"</string> <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Slikaj"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index 4d6f3ac6a634..6a16246de0a3 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -555,7 +555,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ствараецца новы карыстальнік…"</string> <string name="user_nickname" msgid="262624187455825083">"Псеўданім"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Дадаць госця"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Выдаліць госця"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Госць"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Зрабіць фота"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Выбраць відарыс"</string> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index ef0412b1485d..07ba66a59e92 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Оставащо време до пълно зареждане: <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до пълно зареждане"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оптимизиране за състоян. на батерията"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оптимизиране с цел състоянието на батерията"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Зарежда се бързо"</string> @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Създава се нов потребител…"</string> <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Добавяне на гост"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Премахване на госта"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Прекратяване на сесията като гост"</string> <string name="guest_nickname" msgid="6332276931583337261">"Гост"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Правене на снимка"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Избиране на изображение"</string> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index 5b95399e83b8..99ee1d646346 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যবহারকারী তৈরি করা হচ্ছে…"</string> <string name="user_nickname" msgid="262624187455825083">"বিশেষ নাম"</string> <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ করুন"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি সরান"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ফটো তুলুন"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"একটি ইমেজ বেছে নিন"</string> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index 00d8087e5483..eedad341189e 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -554,7 +554,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kreiranje novog korisnika…"</string> <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Snimite fotografiju"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberite sliku"</string> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 98d502d050eb..209b2d601933 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> per completar la càrrega"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g>: optimitzant per a l\'estat de la bateria"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g>: s\'està optimitzant per preservar l\'estat de la bateria"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconegut"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"S\'està carregant"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregant ràpidament"</string> @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"S\'està creant l\'usuari…"</string> <string name="user_nickname" msgid="262624187455825083">"Àlies"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Afegeix un convidat"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Suprimeix el convidat"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Convidat"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fes una foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Tria una imatge"</string> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index a9a5f483eaa3..b3141e405860 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -555,7 +555,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytváření nového uživatele…"</string> <string name="user_nickname" msgid="262624187455825083">"Přezdívka"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Přidat hosta"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranit hosta"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Host"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Pořídit fotku"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrat obrázek"</string> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index 738ed3c697a8..a7fa91f34dda 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Opretter ny bruger…"</string> <string name="user_nickname" msgid="262624187455825083">"Kaldenavn"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Tilføj gæsten"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gæsten"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Gæst"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Tag et billede"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Vælg et billede"</string> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index e274d639b33c..361b932cea48 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Neuer Nutzer wird erstellt…"</string> <string name="user_nickname" msgid="262624187455825083">"Alias"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Gast hinzufügen"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Gast entfernen"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Gast"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Foto machen"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Bild auswählen"</string> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 66244eb3b0f6..a3b3d26c92c5 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Απομένουν <xliff:g id="TIME">%1$s</xliff:g> για ολοκλήρωση της φόρτισης"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> για την ολοκλήρωση της φόρτισης"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Βελτιστοποίηση κατάστασης μπαταρίας"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Βελτιστοποίηση για τη διατήρηση της καλής κατάστασης της μπαταρίας"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Άγνωστο"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Φόρτιση"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ταχεία φόρτιση"</string> @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Δημιουργία νέου χρήστη…"</string> <string name="user_nickname" msgid="262624187455825083">"Ψευδώνυμο"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Προσθήκη επισκέπτη"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Κατάργηση επισκέπτη"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Επισκέπτης"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Λήψη φωτογραφίας"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Επιλογή εικόνας"</string> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index f5a0e2c1fb9b..fe7515f68954 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string> <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string> diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index 6f11e804e893..75a66781131b 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string> <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index f5a0e2c1fb9b..fe7515f68954 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string> <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index f5a0e2c1fb9b..fe7515f68954 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string> <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string> diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml index bfca10955c4b..aed0800ba197 100644 --- a/packages/SettingsLib/res/values-en-rXC/strings.xml +++ b/packages/SettingsLib/res/values-en-rXC/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"End guest session"</string> <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 44ae8c2e6545..f9260a8745af 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario nuevo…"</string> <string name="user_nickname" msgid="262624187455825083">"Sobrenombre"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Agregar invitado"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string> <string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Tomar una foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Elegir una imagen"</string> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index 236394c5e641..bc176234e09b 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario…"</string> <string name="user_nickname" msgid="262624187455825083">"Apodo"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Añadir invitado"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string> <string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Hacer foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Seleccionar una imagen"</string> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index c8fd94f72849..c73f1365c8c3 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Uue kasutaja loomine …"</string> <string name="user_nickname" msgid="262624187455825083">"Hüüdnimi"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Lisa külaline"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Eemalda külaline"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Külaline"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Pildistage"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Valige pilt"</string> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 226ffba24621..2f51ac03ed77 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> guztiz kargatu arte"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Optimizatzen bateria egoera onean mantentzeko"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Optimizatzen, bateria egoera onean mantentzeko"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ezezaguna"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Kargatzen"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Bizkor kargatzen"</string> @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Beste erabiltzaile bat sortzen…"</string> <string name="user_nickname" msgid="262624187455825083">"Goitizena"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Gehitu gonbidatua"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Kendu gonbidatua"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Amaitu gonbidatuentzako saioa"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gonbidatua"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Atera argazki bat"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Aukeratu irudi bat"</string> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 917a637f1e01..038b81503c89 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"درحال ایجاد کاربر جدید…"</string> <string name="user_nickname" msgid="262624187455825083">"نام مستعار"</string> <string name="guest_new_guest" msgid="3482026122932643557">"افزودن مهمان"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"حذف مهمان"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"مهمان"</string> <string name="user_image_take_photo" msgid="467512954561638530">"عکس گرفتن"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"انتخاب تصویر"</string> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index b1038025bf1e..33b6142194dd 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Luodaan uutta käyttäjää…"</string> <string name="user_nickname" msgid="262624187455825083">"Lempinimi"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Lisää vieras"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Poista vieras"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Vieras"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Ota kuva"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Valitse kuva"</string> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 91d14d40e269..01492c2f9719 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> jusqu\'à la charge complète"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> : <xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la charge complète"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimisation pour la santé de la pile"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimisation pour préserver la pile"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charge en cours…"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Recharge rapide"</string> @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Créer un utilisateur…"</string> <string name="user_nickname" msgid="262624187455825083">"Pseudo"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Mettre fin à la session d\'invité"</string> <string name="guest_nickname" msgid="6332276931583337261">"Invité"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Sélectionner une image"</string> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 2518fe10d06e..04ed5fee3bde 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Création d\'un nouvel utilisateur…"</string> <string name="user_nickname" msgid="262624187455825083">"Pseudo"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Invité"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Choisir une image"</string> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index 486afe346ac2..b59395cf89e9 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> para completar a carga"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> para completar a carga"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g>: optimizando para manter a batería en bo estado"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g>: optimizando a preservación da batería"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Descoñecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rapidamente"</string> @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario novo…"</string> <string name="user_nickname" msgid="262624187455825083">"Alcume"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Engadir convidado"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar convidado"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Tirar foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Escoller imaxe"</string> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index 2bebe36818c5..601ffde9d50e 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"નવા વપરાશકર્તા બનાવી રહ્યાં છીએ…"</string> <string name="user_nickname" msgid="262624187455825083">"ઉપનામ"</string> <string name="guest_new_guest" msgid="3482026122932643557">"અતિથિ ઉમેરો"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"અતિથિને કાઢી નાખો"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"અતિથિ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ફોટો લો"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"છબી પસંદ કરો"</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 824ab25a5bcc..974d00cd10f8 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नया उपयोगकर्ता बनाया जा रहा है…"</string> <string name="user_nickname" msgid="262624187455825083">"प्रचलित नाम"</string> <string name="guest_new_guest" msgid="3482026122932643557">"मेहमान जोड़ें"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"मेहमान हटाएं"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"मेहमान के तौर पर ब्राउज़ करने का सेशन खत्म करें"</string> <string name="guest_nickname" msgid="6332276931583337261">"मेहमान"</string> <string name="user_image_take_photo" msgid="467512954561638530">"फ़ोटो खींचें"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"कोई इमेज चुनें"</string> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index cd21a5fe1da5..f7924285f9ee 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -554,7 +554,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Izrada novog korisnika…"</string> <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Dodavanje gosta"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Uklanjanje gosta"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fotografiraj"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index c3b859cb0724..2bf5325b24ff 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Új felhasználó létrehozása…"</string> <string name="user_nickname" msgid="262624187455825083">"Becenév"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Vendég hozzáadása"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Vendég munkamenet eltávolítása"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Vendég"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fotó készítése"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Kép kiválasztása"</string> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index 041ad0e191d5..fea1e128d216 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> մինչև լիցքավորումը"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լիցքավորումը"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Օպտիմալացվում է"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Օպտիմալացվում է մարտկոցի պահպանման համար"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Անհայտ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Լիցքավորում"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Արագ լիցքավորում"</string> @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ստեղծվում է օգտատիրոջ նոր պրոֆիլ…"</string> <string name="user_nickname" msgid="262624187455825083">"Կեղծանուն"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Ավելացնել հյուր"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Հեռացնել հյուրին"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Ավարտել հյուրի աշխատաշրջանը"</string> <string name="guest_nickname" msgid="6332276931583337261">"Հյուր"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Լուսանկարել"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Ընտրել պատկեր"</string> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index 332d5b35e857..7298cea4e00d 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Membuat pengguna baru …"</string> <string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Tambahkan tamu"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Hapus tamu"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Tamu"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih gambar"</string> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index f40136452674..bf6c03167476 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Stofnar nýjan notanda…"</string> <string name="user_nickname" msgid="262624187455825083">"Gælunafn"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Bæta gesti við"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Fjarlægja gest"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Gestur"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Taka mynd"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Velja mynd"</string> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index ef5e2ff912ad..2ed92e4be0b8 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creazione nuovo utente…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Aggiungi ospite"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Rimuovi ospite"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Ospite"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Scatta una foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Scegli un\'immagine"</string> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index db2ffe5d2f17..bf4c35e000ef 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -555,7 +555,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"בתהליך יצירה של משתמש חדש…"</string> <string name="user_nickname" msgid="262624187455825083">"כינוי"</string> <string name="guest_new_guest" msgid="3482026122932643557">"הוספת אורח"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"הסרת אורח"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"אורח"</string> <string name="user_image_take_photo" msgid="467512954561638530">"צילום תמונה"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"לבחירת תמונה"</string> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index 9e9db3d2be38..f467c6049722 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"新しいユーザーを作成しています…"</string> <string name="user_nickname" msgid="262624187455825083">"ニックネーム"</string> <string name="guest_new_guest" msgid="3482026122932643557">"ゲストを追加"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"ゲストを削除"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"ゲスト セッションを終了する"</string> <string name="guest_nickname" msgid="6332276931583337261">"ゲスト"</string> <string name="user_image_take_photo" msgid="467512954561638530">"写真を撮る"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"画像を選択"</string> diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index ec3900883140..493839f0a07f 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"მიმდინარეობს ახალი მომხმარებლის შექმნა…"</string> <string name="user_nickname" msgid="262624187455825083">"მეტსახელი"</string> <string name="guest_new_guest" msgid="3482026122932643557">"სტუმრის დამატება"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"სტუმრის ამოშლა"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"სტუმარი"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ფოტოს გადაღება"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"აირჩიეთ სურათი"</string> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index 881a13cf1cba..83d859b83255 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Зарядталғанға дейін <xliff:g id="TIME">%1$s</xliff:g> қалды"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядталғанға дейін <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батареяның жұмыс істеу қабілеті оңтайландырылуда"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батарея жұмысын оңтайландыру"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгісіз"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарядталуда"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Жылдам зарядталуда"</string> @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңа пайдаланушы профилі жасалуда…"</string> <string name="user_nickname" msgid="262624187455825083">"Лақап ат"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Қонақты енгізу"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Қонақты өшіру"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Қонақ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Фотосуретке түсіру"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Сурет таңдау"</string> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index 14f77889d83d..db571d6f2dfe 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"កំពុងបង្កើតអ្នកប្រើប្រាស់ថ្មី…"</string> <string name="user_nickname" msgid="262624187455825083">"ឈ្មោះហៅក្រៅ"</string> <string name="guest_new_guest" msgid="3482026122932643557">"បញ្ចូលភ្ញៀវ"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"លុបភ្ញៀវ"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"ភ្ញៀវ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ថតរូប"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ជ្រើសរើសរូបភាព"</string> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index a279d22538a1..2e8b9f1438d9 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"ಚಾರ್ಜ್ ಆಗಲು <xliff:g id="TIME">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಇದೆ"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜ್ ಆಗಲು <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯ ಬೇಕು"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಬ್ಯಾಟರಿಯ ಆರೋಗ್ಯಕ್ಕಾಗಿ ಆಪ್ಟಿಮೈಸ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಬ್ಯಾಟರಿಯ ಸುಸ್ಥಿತಿಗಾಗಿ ಆಪ್ಟಿಮೈಸ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ಅಪರಿಚಿತ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ವೇಗದ ಚಾರ್ಜಿಂಗ್"</string> @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ಹೊಸ ಬಳಕೆದಾರರನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ…"</string> <string name="user_nickname" msgid="262624187455825083">"ಅಡ್ಡ ಹೆಸರು"</string> <string name="guest_new_guest" msgid="3482026122932643557">"ಅತಿಥಿಯನ್ನು ಸೇರಿಸಿ"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"ಅತಿಥಿಯನ್ನು ತೆಗೆದುಹಾಕಿ"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"ಅತಿಥಿ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ಫೋಟೋ ತೆಗೆದುಕೊಳ್ಳಿ"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ಚಿತ್ರವನ್ನು ಆರಿಸಿ"</string> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index de694884466b..0294e9d4d8c3 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"새로운 사용자를 만드는 중…"</string> <string name="user_nickname" msgid="262624187455825083">"닉네임"</string> <string name="guest_new_guest" msgid="3482026122932643557">"게스트 추가"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"게스트 삭제"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"게스트"</string> <string name="user_image_take_photo" msgid="467512954561638530">"사진 찍기"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"이미지 선택"</string> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index e70460ffa06d..0c73cf67c517 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңы колдонуучу түзүлүүдө…"</string> <string name="user_nickname" msgid="262624187455825083">"Ылакап аты"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Конок кошуу"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Конокту өчүрүү"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Конок"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Сүрөткө тартуу"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Сүрөт тандаңыз"</string> diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index c63cec8fc7a2..fbe814ab619b 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ກຳລັງສ້າງຜູ້ໃຊ້ໃໝ່…"</string> <string name="user_nickname" msgid="262624187455825083">"ຊື່ຫຼິ້ນ"</string> <string name="guest_new_guest" msgid="3482026122932643557">"ເພີ່ມແຂກ"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"ລຶບແຂກອອກ"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"ແຂກ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ຖ່າຍຮູບ"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ເລືອກຮູບ"</string> diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index d040edf25b3b..b3d8e171d22e 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -555,7 +555,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kuriamas naujas naudotojas…"</string> <string name="user_nickname" msgid="262624187455825083">"Slapyvardis"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Pridėti svečią"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Pašalinti svečią"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Svečias"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fotografuoti"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Pasirinkti vaizdą"</string> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index 74c0dd5ccad6..3c69e642156a 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -554,7 +554,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Notiek jauna lietotāja izveide…"</string> <string name="user_nickname" msgid="262624187455825083">"Segvārds"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Pievienot viesi"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Noņemt viesi"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Viesis"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Uzņemt fotoattēlu"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Izvēlēties attēlu"</string> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index 739bceb81e1a..b909359faf3b 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Се создава нов корисник…"</string> <string name="user_nickname" msgid="262624187455825083">"Прекар"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Додај гостин"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Отстрани гостин"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Гостин"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Фотографирајте"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Одберете слика"</string> diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index b7de4293bc1f..8f83ee103de3 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"പുതിയ ഉപയോക്താവിനെ സൃഷ്ടിക്കുന്നു…"</string> <string name="user_nickname" msgid="262624187455825083">"വിളിപ്പേര്"</string> <string name="guest_new_guest" msgid="3482026122932643557">"അതിഥിയെ ചേർക്കുക"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"അതിഥിയെ നീക്കം ചെയ്യുക"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"അതിഥി"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ഒരു ഫോട്ടോ എടുക്കുക"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ഒരു ചിത്രം തിരഞ്ഞെടുക്കുക"</string> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index c7a2edd7c3fb..8168b7922e63 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Цэнэглэх хүртэл үлдсэн <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - цэнэглэх хүртэл <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батарейн чанарыг оновчилж байна"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батарейн барилтыг оновчилж байна"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Тодорхойгүй"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Цэнэглэж байна"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хурдан цэнэглэж байна"</string> @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Шинэ хэрэглэгч үүсгэж байна…"</string> <string name="user_nickname" msgid="262624187455825083">"Хоч"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Зочин нэмэх"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Зочин хасах"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Зочны сургалтыг дуусгах"</string> <string name="guest_nickname" msgid="6332276931583337261">"Зочин"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Зураг авах"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Зураг сонгох"</string> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index d29cb590da81..f47d8e005508 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नवीन वापरकर्ता तयार करत आहे…"</string> <string name="user_nickname" msgid="262624187455825083">"टोपणनाव"</string> <string name="guest_new_guest" msgid="3482026122932643557">"अतिथी जोडा"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथी काढून टाका"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"अतिथी"</string> <string name="user_image_take_photo" msgid="467512954561638530">"फोटो काढा"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"इमेज निवडा"</string> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index c593e4b8f9d9..fb2826f9609c 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Mencipta pengguna baharu…"</string> <string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Tambah tetamu"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Alih keluar tetamu"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Tamatkan sesi tetamu"</string> <string name="guest_nickname" msgid="6332276931583337261">"Tetamu"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih imej"</string> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index aa9d3d63fb6c..d98d4429b314 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"အသုံးပြုသူအသစ် ပြုလုပ်နေသည်…"</string> <string name="user_nickname" msgid="262624187455825083">"နာမည်ပြောင်"</string> <string name="guest_new_guest" msgid="3482026122932643557">"ဧည့်သည့် ထည့်ရန်"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"ဧည့်သည်ကို ဖယ်ထုတ်ရန်"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"ဧည့်သည်"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ဓာတ်ပုံရိုက်ရန်"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ပုံရွေးရန်"</string> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index 9494c398a93b..944c48ebdde2 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> til batteriet er fulladet"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> til batteriet er fulladet"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimaliserer batteritilstanden"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – optimaliserer batteritilstanden"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukjent"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Lader"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Lader raskt"</string> @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Oppretter en ny bruker …"</string> <string name="user_nickname" msgid="262624187455825083">"Kallenavn"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Legg til en gjest"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gjesten"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Gjest"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Ta et bilde"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Velg et bilde"</string> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index e31fb4eff9b1..9a0d2eda8626 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नयाँ प्रयोगकर्ता बनाउँदै…"</string> <string name="user_nickname" msgid="262624187455825083">"उपनाम"</string> <string name="guest_new_guest" msgid="3482026122932643557">"अतिथि थप्नुहोस्"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथि हटाउनुहोस्"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"अतिथि"</string> <string name="user_image_take_photo" msgid="467512954561638530">"फोटो खिच्नुहोस्"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"कुनै फोटो छनौट गर्नुहोस्"</string> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index 0b75fdd16a29..a8e4fb5b2d26 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Nieuwe gebruiker maken…"</string> <string name="user_nickname" msgid="262624187455825083">"Bijnaam"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Gast toevoegen"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Gast verwijderen"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Gastsessie beëindigen"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gast"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Foto maken"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Afbeelding kiezen"</string> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index cfde61f86677..306ef440a02c 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ନୂଆ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରାଯାଉଛି…"</string> <string name="user_nickname" msgid="262624187455825083">"ଡାକନାମ"</string> <string name="guest_new_guest" msgid="3482026122932643557">"ଅତିଥି ଯୋଗ କରନ୍ତୁ"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"ଅତିଥିଙ୍କୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"ଅତିଥି"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ଗୋଟିଏ ଫଟୋ ଉଠାନ୍ତୁ"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ଏକ ଛବି ବାଛନ୍ତୁ"</string> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index 122186256a1e..e231a26671e6 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ਤੱਕ ਚਾਰਜ ਹੋ ਜਾਵੇਗੀ"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਸਥਿਤੀ ਲਈ ਅਨੁਕੂਲ ਬਣਾਇਆ ਜਾ ਰਿਹਾ"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਦੀ ਸਥਿਤੀ ਲਈ ਅਨੁਕੂਲ ਬਣਾਇਆ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ਅਗਿਆਤ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string> @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਬਣਾਇਆ ਜਾ ਰਿਹਾ ਹੈ…"</string> <string name="user_nickname" msgid="262624187455825083">"ਉਪਨਾਮ"</string> <string name="guest_new_guest" msgid="3482026122932643557">"ਮਹਿਮਾਨ ਸ਼ਾਮਲ ਕਰੋ"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"ਮਹਿਮਾਨ ਹਟਾਓ"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"ਮਹਿਮਾਨ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ਇੱਕ ਫ਼ੋਟੋ ਖਿੱਚੋ"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ਕੋਈ ਚਿੱਤਰ ਚੁਣੋ"</string> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index 102835c9bd97..a62e9cc440e1 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Do naładowania <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – do naładowania <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optymalizuję, by utrzymać baterię w dobrym stanie"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optymalizuję, aby utrzymać baterię w dobrym stanie"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Szybkie ładowanie"</string> @@ -555,7 +555,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Tworzę nowego użytkownika…"</string> <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gościa"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Usuń gościa"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Gość"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Zrób zdjęcie"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Wybierz obraz"</string> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index 99019a683d96..fe922c0926ec 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string> <string name="user_nickname" msgid="262624187455825083">"Apelido"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 238e54bb5fd3..5d1f88049160 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"A criar novo utilizador…"</string> <string name="user_nickname" msgid="262624187455825083">"Pseudónimo"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index 99019a683d96..fe922c0926ec 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string> <string name="user_nickname" msgid="262624187455825083">"Apelido"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index df95dbc76ad4..765f83a59a3a 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -554,7 +554,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Se creează un utilizator nou…"</string> <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Adăugați un invitat"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Ștergeți invitatul"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Invitat"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Faceți o fotografie"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Alegeți o imagine"</string> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 68826d7552fc..98b320394c0a 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -555,7 +555,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Создаем нового пользователя…"</string> <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Добавить аккаунт гостя"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Удалить аккаунт гостя"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Гость"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Сделать снимок"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Выбрать фото"</string> diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index 736445da0910..087f140362b7 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"ආරෝපණය වන තෙක් <xliff:g id="TIME">%1$s</xliff:g> ඇත"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය වන තෙක් <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - බැටරි සෞඛ්යය සඳහා ප්රශස්ත කරමින්"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - බැටරි ආයු කාලය වැඩි දියුණු කරමින්"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"නොදනී"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ආරෝපණය වෙමින්"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ශීඝ්ර ආරෝපණය"</string> @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"නව පරිශීලක තනමින්…"</string> <string name="user_nickname" msgid="262624187455825083">"අපනාමය"</string> <string name="guest_new_guest" msgid="3482026122932643557">"අමුත්තා එක් කරන්න"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"අමුත්තා ඉවත් කරන්න"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"අමුත්තා"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ඡායාරූපයක් ගන්න"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"රූපයක් තෝරන්න"</string> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 265ec8fcff04..858f01758a86 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -555,7 +555,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytvára sa nový používateľ…"</string> <string name="user_nickname" msgid="262624187455825083">"Prezývka"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Pridať hosťa"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Odobrať hosťa"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Hosť"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Odfotiť"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrať obrázok"</string> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index 2e6fc0ecdfe1..2386b13873e8 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Še <xliff:g id="TIME">%1$s</xliff:g> do polne napolnjenosti"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do polne napolnjenosti"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimizacija za ohran. zmog. baterije"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimizacija za ohranjanje zmogljivosti baterije"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznano"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Polnjenje"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hitro polnjenje"</string> @@ -555,7 +555,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ustvarjanje novega uporabnika …"</string> <string name="user_nickname" msgid="262624187455825083">"Vzdevek"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Dodajanje gosta"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranitev gosta"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fotografiranje"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Izberi sliko"</string> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index 821e897b6417..8d53b256f874 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Po krijohet një përdorues i ri…"</string> <string name="user_nickname" msgid="262624187455825083">"Pseudonimi"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Shto të ftuar"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Hiq të ftuarin"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Jepi fund sesionit të vizitorit"</string> <string name="guest_nickname" msgid="6332276931583337261">"I ftuar"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Bëj një fotografi"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Zgjidh një imazh"</string> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index 207ca7437e5a..0b64a705cd7d 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"Напуниће се за <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – напуниће се за <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оптимизује се ради стања батерије"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оптимизује се ради бољег стања батерије"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Пуни се"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо се пуни"</string> @@ -554,7 +554,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Прави се нови корисник…"</string> <string name="user_nickname" msgid="262624187455825083">"Надимак"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Додај госта"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Уклони госта"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Гост"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Сликај"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Одабери слику"</string> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index 7afd34464620..58eda86276e3 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skapar ny användare …"</string> <string name="user_nickname" msgid="262624187455825083">"Smeknamn"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Lägg till gäst"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Ta bort gäst"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Gäst"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Ta ett foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Välj en bild"</string> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index b08da895c4b1..889163bcbd40 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -492,8 +492,8 @@ <string name="status_unavailable" msgid="5279036186589861608">"Hamna"</string> <string name="wifi_status_mac_randomized" msgid="466382542497832189">"Imechagua anwani ya MAC kwa nasibu"</string> <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139"> - <item quantity="other">Imeunganisha vifaa %1$d</item> - <item quantity="one">Imeunganisha kifaa %1$d</item> + <item quantity="other">Vifaa %1$d vimeunganishwa</item> + <item quantity="one">Kifaa %1$d kimeunganishwa</item> </plurals> <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Muda zaidi."</string> <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Muda kidogo."</string> @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Inaweka mtumiaji mpya…"</string> <string name="user_nickname" msgid="262624187455825083">"Jina wakilishi"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Weka mgeni"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Ondoa mgeni"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Mgeni"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Piga picha"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Chagua picha"</string> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 5fdf8bd61308..e0de7782f5b2 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"புதிய பயனரை உருவாக்குகிறது…"</string> <string name="user_nickname" msgid="262624187455825083">"புனைப்பெயர்"</string> <string name="guest_new_guest" msgid="3482026122932643557">"கெஸ்ட்டைச் சேர்"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"கெஸ்ட்டை அகற்று"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"கெஸ்ட்"</string> <string name="user_image_take_photo" msgid="467512954561638530">"படமெடுங்கள்"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"படத்தைத் தேர்வுசெய்யுங்கள்"</string> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 38f08b2b8603..53f4abf90079 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"ఛార్జ్ అవ్వడానికి <xliff:g id="TIME">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జ్ అవ్వడానికి <xliff:g id="TIME">%2$s</xliff:g> పడుతుంది"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - బ్యాటరీ స్థితిని ఆప్టిమైజ్ చేయడం కోసం"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - బ్యాటరీ జీవితకాలాన్ని పెంచడం కోసం ఆప్టిమైజ్ చేస్తోంది"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"తెలియదు"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ఛార్జ్ అవుతోంది"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"వేగవంతమైన ఛార్జింగ్"</string> @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"కొత్త యూజర్ను క్రియేట్ చేస్తోంది…"</string> <string name="user_nickname" msgid="262624187455825083">"మారుపేరు"</string> <string name="guest_new_guest" msgid="3482026122932643557">"అతిథిని జోడించండి"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"అతిథిని తీసివేయండి"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"అతిథి"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ఒక ఫోటో తీయండి"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ఇమేజ్ను ఎంచుకోండి"</string> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index a8951388e488..5f93563229fa 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -284,7 +284,7 @@ <string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"เพิ่มระดับการบันทึก Wi‑Fi แสดงต่อ SSID RSSI ในตัวเลือก Wi‑Fi"</string> <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"ลดการเปลืองแบตเตอรี่และเพิ่มประสิทธิภาพเครือข่าย"</string> <string name="wifi_enhanced_mac_randomization_summary" msgid="1210663439867489931">"เมื่อเปิดใช้โหมดนี้ ที่อยู่ MAC ของอุปกรณ์นี้อาจเปลี่ยนทุกครั้งที่เชื่อมต่อกับเครือข่ายที่มีการเปิดใช้การสุ่ม MAC"</string> - <string name="wifi_metered_label" msgid="8737187690304098638">"แบบจำกัดปริมาณอินเทอร์เน็ต"</string> + <string name="wifi_metered_label" msgid="8737187690304098638">"แบบจำกัดปริมาณ"</string> <string name="wifi_unmetered_label" msgid="6174142840934095093">"ไม่มีการวัดปริมาณอินเทอร์เน็ต"</string> <string name="select_logd_size_title" msgid="1604578195914595173">"ขนาดบัฟเฟอร์ของตัวบันทึก"</string> <string name="select_logd_size_dialog_title" msgid="2105401994681013578">"เลือกขนาด Logger ต่อบัฟเฟอร์ไฟล์บันทึก"</string> @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"กำลังสร้างผู้ใช้ใหม่…"</string> <string name="user_nickname" msgid="262624187455825083">"ชื่อเล่น"</string> <string name="guest_new_guest" msgid="3482026122932643557">"เพิ่มผู้เข้าร่วม"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"นำผู้เข้าร่วมออก"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"จบเซสชันผู้เยี่ยมชม"</string> <string name="guest_nickname" msgid="6332276931583337261">"ผู้ใช้ชั่วคราว"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ถ่ายรูป"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"เลือกรูปภาพ"</string> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index b451d894d254..9bbfb1d82f68 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Gumagawa ng bagong user…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Magdagdag ng bisita"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Alisin ang bisita"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Tapusin ang session ng bisita"</string> <string name="guest_nickname" msgid="6332276931583337261">"Bisita"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Kumuha ng larawan"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Pumili ng larawan"</string> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index e1ff56baf17d..9dd32b6f78ea 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni kullanıcı oluşturuluyor…"</string> <string name="user_nickname" msgid="262624187455825083">"Takma ad"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Misafir ekle"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Misafir oturumunu kaldır"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Misafir oturumunu sonlandır"</string> <string name="guest_nickname" msgid="6332276931583337261">"Misafir"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fotoğraf çek"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Resim seç"</string> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index 6806ce6f1f4a..99cc95871346 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> до повного заряду"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – стан акумулятора оптимізується"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оптимізація для збереження заряду акумулятора"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Невідомо"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Заряджається"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Швидке заряджання"</string> @@ -555,7 +555,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Створення нового користувача…"</string> <string name="user_nickname" msgid="262624187455825083">"Псевдонім"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Додати гостя"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Видалити гостя"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Гість"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Зробити фотографію"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Вибрати зображення"</string> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index 5a8b6dc87006..515cf702cdea 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"نیا صارف تخلیق کرنا…"</string> <string name="user_nickname" msgid="262624187455825083">"عرفی نام"</string> <string name="guest_new_guest" msgid="3482026122932643557">"مہمان کو شامل کریں"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"مہمان کو ہٹائیں"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"مہمان"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ایک تصویر لیں"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ایک تصویر منتخب کریں"</string> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index 7530016763b0..18300468023c 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> ichida toʻladi"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> ichida toʻladi"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Batareya quvvati muvozanatlanmoqda"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> – Batareya uchun optimizatsiya"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Noma’lum"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Quvvat olmoqda"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Tezkor quvvat olmoqda"</string> @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yangi foydalanuvchi yaratilmoqda…"</string> <string name="user_nickname" msgid="262624187455825083">"Nik"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Mehmon kiritish"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Mehmon rejimini olib tashlash"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Mehmon seansini yakunlash"</string> <string name="guest_nickname" msgid="6332276931583337261">"Mehmon"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Suratga olish"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Rasm tanlash"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index 643fdb9f7ab6..2d56e45aabf6 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Đang tạo người dùng mới…"</string> <string name="user_nickname" msgid="262624187455825083">"Biệt hiệu"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Thêm khách"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Xóa phiên khách"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"Kết thúc phiên khách"</string> <string name="guest_nickname" msgid="6332276931583337261">"Khách"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Chụp ảnh"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Chọn một hình ảnh"</string> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 81d8106b484e..6b64d2717266 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在创建新用户…"</string> <string name="user_nickname" msgid="262624187455825083">"昵称"</string> <string name="guest_new_guest" msgid="3482026122932643557">"添加访客"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"移除访客"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"访客"</string> <string name="user_image_take_photo" msgid="467512954561638530">"拍摄照片"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"选择图片"</string> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index 39a12aa23bbd..4ab580e01f28 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"還需 <xliff:g id="TIME">%1$s</xliff:g>才能充滿電"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 還需 <xliff:g id="TIME">%2$s</xliff:g>才能充滿電"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在優化電池狀態"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - 優化電池效能"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"正在快速充電"</string> @@ -553,7 +553,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string> <string name="user_nickname" msgid="262624187455825083">"暱稱"</string> <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string> + <string name="guest_exit_guest" msgid="4754204715192830850">"結束訪客工作階段"</string> <string name="guest_nickname" msgid="6332276931583337261">"訪客"</string> <string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index cc35ed302960..46695dbc88ba 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -450,7 +450,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g>後充飽電"</string> <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽電"</string> - <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - 針對電池狀態進行最佳化調整"</string> + <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - 最佳化調整電池狀態"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string> @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string> <string name="user_nickname" msgid="262624187455825083">"暱稱"</string> <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"訪客"</string> <string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index bcc6369bb99a..f4ff6ec89d94 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -553,7 +553,8 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Idala umsebenzisi omusha…"</string> <string name="user_nickname" msgid="262624187455825083">"Isiteketiso"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Engeza isivakashi"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Susa isihambeli"</string> + <!-- no translation found for guest_exit_guest (4754204715192830850) --> + <skip /> <string name="guest_nickname" msgid="6332276931583337261">"Isihambeli"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Thatha isithombe"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Khetha isithombe"</string> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 0ce8dd85d2ce..792fb52802ff 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1447,6 +1447,9 @@ <!-- Content description of the data connection type 5G+. [CHAR LIMIT=NONE] --> <string name="data_connection_5g_plus" translatable="false">5G+</string> + <!-- Content description of the data connection type Carrier WiFi. [CHAR LIMIT=NONE] --> + <string name="data_connection_carrier_wifi">CWF</string> + <!-- Content description of the cell data being disabled. [CHAR LIMIT=NONE] --> <string name="cell_data_off_content_description">Mobile data off</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java index a92105320a62..02d1c2e65c8d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java @@ -23,12 +23,12 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.net.wifi.WifiManager; +import android.net.wifi.WifiManager.SubsystemRestartTrackingCallback; import android.os.Handler; import android.os.HandlerExecutor; import android.provider.Settings; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; -import android.text.TextUtils; import android.util.Log; /** @@ -58,35 +58,18 @@ public class ConnectivitySubsystemsRecoveryManager { private boolean mWifiRestartInProgress = false; private boolean mTelephonyRestartInProgress = false; private RecoveryStatusCallback mCurrentRecoveryCallback = null; - private final BroadcastReceiver mWifiMonitor = new BroadcastReceiver() { + private final SubsystemRestartTrackingCallback mWifiSubsystemRestartTrackingCallback = + new SubsystemRestartTrackingCallback() { @Override - public void onReceive(Context context, Intent intent) { - if (!mWifiRestartInProgress || mCurrentRecoveryCallback == null) { - stopTrackingWifiRestart(); - } - - // TODO: harden this code to avoid race condition. What if WiFi toggled just before - // recovery triggered. Either use new broadcasts from framework or detect more state - // changes. - boolean recoveryDone = false; - if (TextUtils.equals(intent.getAction(), WifiManager.WIFI_STATE_CHANGED_ACTION)) { - if (intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, - WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED) { - recoveryDone = true; - } - } else if (TextUtils.equals(intent.getAction(), - WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) { - if (intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE, - WifiManager.WIFI_AP_STATE_FAILED) == WifiManager.WIFI_AP_STATE_ENABLED) { - recoveryDone = true; - } - } + public void onSubsystemRestarting() { + // going to do nothing on this - already assuming that subsystem is restarting + } - if (recoveryDone) { - mWifiRestartInProgress = false; - stopTrackingWifiRestart(); - checkIfAllSubsystemsRestartsAreDone(); - } + @Override + public void onSubsystemRestarted() { + mWifiRestartInProgress = false; + stopTrackingWifiRestart(); + checkIfAllSubsystemsRestartsAreDone(); } }; private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { @@ -208,13 +191,13 @@ public class ConnectivitySubsystemsRecoveryManager { } private void startTrackingWifiRestart() { - IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION); - filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); - mContext.registerReceiver(mWifiMonitor, filter, null, mHandler); + mWifiManager.registerSubsystemRestartTrackingCallback(new HandlerExecutor(mHandler), + mWifiSubsystemRestartTrackingCallback); } private void stopTrackingWifiRestart() { - mContext.unregisterReceiver(mWifiMonitor); + mWifiManager.unregisterWifiSubsystemRestartTrackingCallback( + mWifiSubsystemRestartTrackingCallback); } private void startTrackingTelephonyRestart() { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java index 61af5a74b00a..647fd2acf7c8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java @@ -19,7 +19,7 @@ package com.android.settingslib.media; /** * Class to access MediaOutput constants. */ -public class MediaOutputSliceConstants { +public class MediaOutputConstants { /** * Key for the Remote Media slice. diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java index c2613a5f4a4a..0cb9906b9a82 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java @@ -43,6 +43,7 @@ public class TelephonyIcons { public static final int ICON_1X = R.drawable.ic_1x_mobiledata; public static final int ICON_5G = R.drawable.ic_5g_mobiledata; public static final int ICON_5G_PLUS = R.drawable.ic_5g_plus_mobiledata; + public static final int ICON_CWF = R.drawable.ic_carrier_wifi; public static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup( "CARRIER_NETWORK_CHANGE", @@ -276,6 +277,20 @@ public class TelephonyIcons { 0, false); + public static final MobileIconGroup CARRIER_MERGED_WIFI = new MobileIconGroup( + "CWF", + /* sbIcons= */ null, + /* qsIcons= */ null, + AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH, + /* sbNullState= */ 0, + /* qsNullState= */ 0, + /* sbDiscState= */ 0, + /* qsDiscState= */ 0, + AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0], + R.string.data_connection_carrier_wifi, + TelephonyIcons.ICON_CWF, + /* isWide= */ true); + // When adding a new MobileIconGround, check if the dataContentDescription has to be filtered // in QSCarrier#hasValidTypeContentDescription diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index cbb510505fdd..4614694a2bd8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -33,6 +33,7 @@ import android.net.wifi.WifiNetworkScoreCache; import android.os.Handler; import android.os.Looper; import android.provider.Settings; +import android.util.FeatureFlagUtils; import com.android.settingslib.R; @@ -103,11 +104,14 @@ public class WifiStatusTracker { private Network mDefaultNetwork = null; private NetworkCapabilities mDefaultNetworkCapabilities = null; private final Runnable mCallback; + private final boolean mProviderModel; private WifiInfo mWifiInfo; public boolean enabled; public boolean isCaptivePortal; public boolean isDefaultNetwork; + public boolean isCarrierMerged; + public int subId; public int state; public boolean connected; public String ssid; @@ -124,6 +128,8 @@ public class WifiStatusTracker { mNetworkScoreManager = networkScoreManager; mConnectivityManager = connectivityManager; mCallback = callback; + mProviderModel = FeatureFlagUtils.isEnabled( + mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL); } public void setListening(boolean listening) { @@ -193,6 +199,10 @@ public class WifiStatusTracker { } else { ssid = getValidSsid(mWifiInfo); } + if (mProviderModel) { + isCarrierMerged = mWifiInfo.isCarrierMerged(); + subId = mWifiInfo.getSubscriptionId(); + } updateRssi(mWifiInfo.getRssi()); maybeRequestNetworkScore(); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java index 99d2ff7f322f..695b5b6ba061 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java @@ -18,15 +18,21 @@ package com.android.settingslib.emergencynumber; import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE; +import static com.android.settingslib.emergencynumber.EmergencyNumberUtils.EMERGENCY_GESTURE_CALL_NUMBER; + import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; -import android.provider.Settings; +import android.net.Uri; +import android.os.Bundle; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.emergency.EmergencyNumber; @@ -38,7 +44,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; import java.util.List; @@ -55,12 +60,15 @@ public class EmergencyNumberUtilsTest { private PackageManager mPackageManager; @Mock private TelephonyManager mTelephonyManager; + @Mock + ContentResolver mContentResolver; private EmergencyNumberUtils mUtils; @Before public void setup() { MockitoAnnotations.initMocks(this); when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mContext.getContentResolver()).thenReturn(mContentResolver); } @Test @@ -89,11 +97,10 @@ public class EmergencyNumberUtilsTest { when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); addEmergencyNumberToTelephony(); - ContentResolver resolver = RuntimeEnvironment.application.getContentResolver(); - when(mContext.getContentResolver()).thenReturn(resolver); - Settings.Secure.putString(resolver, Settings.Secure.EMERGENCY_GESTURE_CALL_NUMBER, - USER_OVERRIDE_EMERGENCY_NUMBER); - + Bundle bundle = new Bundle(); + bundle.putString(EMERGENCY_GESTURE_CALL_NUMBER, USER_OVERRIDE_EMERGENCY_NUMBER); + when(mContentResolver.call(any(Uri.class), anyString(), nullable(String.class), nullable( + Bundle.class))).thenReturn(bundle); mUtils = new EmergencyNumberUtils(mContext); assertThat(mUtils.getPoliceNumber()).isEqualTo(USER_OVERRIDE_EMERGENCY_NUMBER); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index c1c1d65fb9d7..e22f2649d540 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -217,6 +217,7 @@ public class SettingsBackupTest { Settings.Global.DEBUG_APP, Settings.Global.DEBUG_VIEW_ATTRIBUTES, Settings.Global.DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE, + Settings.Global.DECORATED_CUSTOM_VIEW_NOTIF_DECORATION, Settings.Global.DEFAULT_DNS_SERVER, Settings.Global.DEFAULT_INSTALL_LOCATION, Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA, @@ -285,6 +286,7 @@ public class SettingsBackupTest { Settings.Global.WIFI_ON_WHEN_PROXY_DISCONNECTED, Settings.Global.FSTRIM_MANDATORY_INTERVAL, Settings.Global.FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED, + Settings.Global.FULLY_CUSTOM_VIEW_NOTIF_DECORATION, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, Settings.Global.GLOBAL_HTTP_PROXY_HOST, Settings.Global.GLOBAL_HTTP_PROXY_PAC, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index f83f6706d55b..1415119117f8 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -120,6 +120,7 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.CREATE_USERS" /> <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> + <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" /> <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/> <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/> <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"/> @@ -378,6 +379,9 @@ <uses-permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" /> <uses-permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" /> + <!-- Permission required for CTS tests to enable/disable rate limiting toasts. --> + <uses-permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index a06bb931771f..e036d87f2492 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/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml index 19f82480284e..55bdebd2f582 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml @@ -89,7 +89,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/keyguard_status_area" - android:paddingTop="20dp" android:visibility="gone"> <com.android.keyguard.AnimatableClockView android:id="@+id/animatable_clock_view_large" @@ -97,7 +96,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:gravity="center_horizontal" - android:textSize="200dp" + android:textSize="@dimen/large_clock_text_size" android:letterSpacing="0.02" android:lineSpacingMultiplier=".8" android:includeFontPadding="false" diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml index 459d162c65db..c027f8df1f66 100644 --- a/packages/SystemUI/res-keyguard/values-ar/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ar/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-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml index 346874060759..4220526635f7 100644 --- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml @@ -38,7 +38,7 @@ <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Puni se"</string> <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Brzo se puni"</string> <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Sporo se puni"</string> - <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimizuje se radi dobrog stanja baterije"</string> + <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimizuje se radi boljeg stanja baterije"</string> <string name="keyguard_low_battery" msgid="1868012396800230904">"Priključite punjač."</string> <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Pritisnite Meni da biste otključali."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Mreža je zaključana"</string> diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml index 0911efade706..dc4ee693d89c 100644 --- a/packages/SystemUI/res-keyguard/values-bg/strings.xml +++ b/packages/SystemUI/res-keyguard/values-bg/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-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml index 421ddb6d31e6..2e3b4af4a68f 100644 --- a/packages/SystemUI/res-keyguard/values-ca/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml @@ -38,7 +38,7 @@ <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • S\'està carregant"</string> <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • S\'està carregant ràpidament"</string> <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • S\'està carregant lentament"</string> - <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • S\'està optimitzant per mantenir el bon estat de la bateria"</string> + <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • S\'està optimitzant per preservar l\'estat de la bateria"</string> <string name="keyguard_low_battery" msgid="1868012396800230904">"Connecta el carregador."</string> <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Prem Menú per desbloquejar."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"La xarxa està bloquejada"</string> diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml index ce8ef202a038..93fe516a8aa6 100644 --- a/packages/SystemUI/res-keyguard/values-eu/strings.xml +++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml @@ -38,7 +38,7 @@ <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Kargatzen"</string> <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Bizkor kargatzen"</string> <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mantso kargatzen"</string> - <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimizatzen bateria egoera onean mantentzeko"</string> + <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimizatzen, bateria egoera onean mantentzeko"</string> <string name="keyguard_low_battery" msgid="1868012396800230904">"Konektatu kargagailua."</string> <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Desblokeatzeko, sakatu Menua."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Sarea blokeatuta dago"</string> diff --git a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml index f2156252e241..d18690103c96 100644 --- a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml @@ -38,7 +38,7 @@ <string name="keyguard_plugged_in" msgid="8169926454348380863">"En recharge : <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"En recharge rapide : <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"En recharge lente : <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> - <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimisation en fonction de la santé de la pile"</string> + <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimisation pour préserver la pile"</string> <string name="keyguard_low_battery" msgid="1868012396800230904">"Branchez votre chargeur."</string> <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Appuyez sur la touche Menu pour déverrouiller l\'appareil."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Réseau verrouillé"</string> diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml index 01a0fac58016..f8c5ebac9517 100644 --- a/packages/SystemUI/res-keyguard/values-gl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml @@ -38,7 +38,7 @@ <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando"</string> <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando rapidamente"</string> <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando lentamente"</string> - <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimizando para manter a batería en bo estado"</string> + <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimizando a preservación da batería"</string> <string name="keyguard_low_battery" msgid="1868012396800230904">"Conecta o cargador."</string> <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Preme Menú para desbloquear."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Bloqueada pola rede"</string> diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml index c8ed16d10327..ab07ba46c14d 100644 --- a/packages/SystemUI/res-keyguard/values-hy/strings.xml +++ b/packages/SystemUI/res-keyguard/values-hy/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-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml index 3f3bec9b8c8c..bb8a908d20f7 100644 --- a/packages/SystemUI/res-keyguard/values-is/strings.xml +++ b/packages/SystemUI/res-keyguard/values-is/strings.xml @@ -38,7 +38,7 @@ <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Í hleðslu"</string> <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hröð hleðsla"</string> <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hæg hleðsla"</string> - <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Fínstillir til að bæta rafhlöðuendingu"</string> + <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Fínstillir fyrir rafhlöðuendingu"</string> <string name="keyguard_low_battery" msgid="1868012396800230904">"Tengdu hleðslutækið."</string> <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Ýttu á valmyndarhnappinn til að taka úr lás."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Net læst"</string> diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml index 47d17ce57892..c1baadf47de0 100644 --- a/packages/SystemUI/res-keyguard/values-it/strings.xml +++ b/packages/SystemUI/res-keyguard/values-it/strings.xml @@ -38,7 +38,7 @@ <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • In carica"</string> <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ricarica veloce"</string> <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ricarica lenta"</string> - <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • È in corso l\'ottimizzazione per l\'integrità della batteria"</string> + <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ottimizzazione per integrità batteria"</string> <string name="keyguard_low_battery" msgid="1868012396800230904">"Collega il caricabatterie."</string> <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Premi Menu per sbloccare."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rete bloccata"</string> diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml index b20cb05ea86f..8e701fe86430 100644 --- a/packages/SystemUI/res-keyguard/values-kk/strings.xml +++ b/packages/SystemUI/res-keyguard/values-kk/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-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml index a52712379776..96865419cbf7 100644 --- a/packages/SystemUI/res-keyguard/values-kn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-kn/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-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml index 66d0ac756bf5..80761591e0bf 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-keyguard/values-mn/strings.xml b/packages/SystemUI/res-keyguard/values-mn/strings.xml index 2ff4a22d6737..13c152f8f38b 100644 --- a/packages/SystemUI/res-keyguard/values-mn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-mn/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-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml index 43d549c92e6b..04419e039b58 100644 --- a/packages/SystemUI/res-keyguard/values-pa/strings.xml +++ b/packages/SystemUI/res-keyguard/values-pa/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-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml index 76f755c2b96a..01427be041ad 100644 --- a/packages/SystemUI/res-keyguard/values-pl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml @@ -38,7 +38,7 @@ <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ładowanie"</string> <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Szybkie ładowanie"</string> <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Wolne ładowanie"</string> - <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optymalizuję, by utrzymać baterię w dobrym stanie"</string> + <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optymalizuję, aby utrzymać baterię w dobrym stanie"</string> <string name="keyguard_low_battery" msgid="1868012396800230904">"Podłącz ładowarkę."</string> <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Naciśnij Menu, aby odblokować."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Sieć zablokowana"</string> diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml index 3a6078224be2..64609b302069 100644 --- a/packages/SystemUI/res-keyguard/values-si/strings.xml +++ b/packages/SystemUI/res-keyguard/values-si/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-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml index 90b8a3e362fd..7ca04ce8198a 100644 --- a/packages/SystemUI/res-keyguard/values-sr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sr/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-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml index c69a8f56b933..990253a7862f 100644 --- a/packages/SystemUI/res-keyguard/values-te/strings.xml +++ b/packages/SystemUI/res-keyguard/values-te/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-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml index a16576d37e9c..62950e29c70e 100644 --- a/packages/SystemUI/res-keyguard/values-uk/strings.xml +++ b/packages/SystemUI/res-keyguard/values-uk/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-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml index 44e419180f6a..f2ca159bff0d 100644 --- a/packages/SystemUI/res-keyguard/values-uz/strings.xml +++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml @@ -38,7 +38,7 @@ <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Quvvat olmoqda"</string> <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Tezkor quvvat olmoqda"</string> <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Sekin quvvat olmoqda"</string> - <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Batareya quvvati muvozanatlanmoqda"</string> + <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Batareya uchun optimizatsiya"</string> <string name="keyguard_low_battery" msgid="1868012396800230904">"Quvvatlash moslamasini ulang."</string> <string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"Qulfdan chiqarish uchun Menyu tugmasini bosing."</string> <string name="keyguard_network_locked_message" msgid="407096292844868608">"Tarmoq qulflangan"</string> diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml index 1054e36fc048..b4006e8b7341 100644 --- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res-keyguard/values-zh-rHK/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-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml index 7efa2f934d95..757bf56f3050 100644 --- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res-keyguard/values-zh-rTW/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/cmds/idmap2/tests/data/overlay/AndroidManifestNoName.xml b/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml index bc6b733e849f..c83077371bb0 100644 --- a/cmds/idmap2/tests/data/overlay/AndroidManifestNoName.xml +++ b/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2019 The Android Open Source Project +<!-- + Copyright (C) 2020 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,9 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -<manifest +<com.android.systemui.qs.SideLabelTileLayout xmlns:android="http://schemas.android.com/apk/res/android" - package="test.overlay.no.name"> - <overlay - android:targetPackage="test.target"/> -</manifest> + android:id="@+id/tile_page" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false" /> diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestNoNameStatic.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout_side_labels.xml index ed327ce0e630..efa240362f67 100644 --- a/cmds/idmap2/tests/data/overlay/AndroidManifestNoNameStatic.xml +++ b/packages/SystemUI/res/layout/qs_paged_tile_layout_side_labels.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2019 The Android Open Source Project +<!-- + Copyright (C) 2020 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,12 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. --> -<manifest + +<com.android.systemui.qs.PagedTileLayout xmlns:android="http://schemas.android.com/apk/res/android" - package="test.overlay.no.name.static"> - <overlay - android:targetPackage="test.target" - android:targetName="TestResources" - android:isStatic="true" - android:priority="1" /> -</manifest> + 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 81d44cfa49dd..571cbbcc77db 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 000000000000..0d6460c22f0f --- /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 fa4eea5a805f..0beb286b6f63 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Verwyder gas?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Verwyder"</string> + <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 5a22d919e912..f56e84ae1606 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"እንግዳ ይወገድ?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"የእንግዳ ክፍለ-ጊዜ ይብቃ?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"በዚህ ክፍለ-ጊዜ ውስጥ ያሉ ሁሉም መተግበሪያዎች እና ውሂብ ይሰረዛሉ።"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"አስወግድ"</string> + <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 39afa8362861..aaaf7788c5c3 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -357,6 +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> + <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> @@ -462,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"هل تريد إزالة جلسة الضيف؟"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"هل تريد إنهاء جلسة الضيف؟"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"سيتم حذف كل التطبيقات والبيانات في هذه الجلسة."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"إزالة"</string> + <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 cfb300941c74..7e3e3cb04280 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -353,6 +353,8 @@ <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_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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"অতিথি আঁতৰাবনে?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"অতিথিৰ ছেশ্বন সমাপ্ত কৰিবনে?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ সকলো এপ্ আৰু ডেটা মচা হ\'ব।"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"আঁতৰাওক"</string> + <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 fef70c3ff17b..6c22cc34b83e 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Qonaq silinsin?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Yığışdır"</string> + <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 978b46d13c38..2f3d6d28526e 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -354,6 +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> + <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> @@ -456,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Želite li da uklonite gosta?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Ukloni"</string> + <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 641134eb7230..925e86a8916b 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -355,6 +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> + <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> @@ -458,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Выдаліць госця?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Завяршыць гасцявы сеанс?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усе праграмы і даныя гэтага сеанса будуць выдалены."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Выдаліць"</string> + <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 0848383d7785..6a683c742931 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Да се премахне ли гостът?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Да се прекрати ли сесията като гост?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Всички приложения и данни в тази сесия ще бъдат изтрити."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Премахване"</string> + <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 109990189e1e..953ec006016b 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -85,8 +85,7 @@ <string name="screenshot_failed_title" msgid="3259148215671936891">"স্ক্রিনশট সেভ করা যায়নি"</string> <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"স্ক্রিনশট সেভ করার আগে ডিভাইসটি অবশ্যই আনলক করতে হবে"</string> <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"আবার স্ক্রিনশট নেওয়ার চেষ্টা করুন"</string> - <!-- no translation found for screenshot_failed_to_save_text (7232739948999195960) --> - <skip /> + <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"স্ক্রিনশট সেভ করা যায়নি"</string> <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"এই অ্যাপ বা আপনার প্রতিষ্ঠান স্ক্রিনশট নেওয়ার অনুমতি দেয়নি"</string> <string name="screenshot_edit_label" msgid="8754981973544133050">"এডিট করুন"</string> <string name="screenshot_edit_description" msgid="3333092254706788906">"স্ক্রিনশট এডিট করুন"</string> @@ -354,6 +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> + <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> @@ -455,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"অতিথি সরাবেন?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"গেস্ট সেশন শেষ করতে চান?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই সেশনের সব অ্যাপ্লিকেশান ও ডেটা মুছে ফেলা হবে।"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"সরান"</string> + <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 3c81a4b1deb9..6bf185230602 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -354,6 +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> + <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> @@ -456,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Želite li ukloniti gosta?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Ukloni"</string> + <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 ef419ab2bd79..71105cc55b50 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Vols suprimir el convidat?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Suprimeix"</string> + <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 972232fd8768..65e676737ca2 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -355,6 +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> + <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> @@ -458,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Odstranit hosta?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Odstranit"</string> + <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-cs/strings_tv.xml b/packages/SystemUI/res/values-cs/strings_tv.xml index 0a9850c7b7a7..eeab0d9d95a2 100644 --- a/packages/SystemUI/res/values-cs/strings_tv.xml +++ b/packages/SystemUI/res/values-cs/strings_tv.xml @@ -23,7 +23,7 @@ <string name="app_accessed_mic" msgid="2754428675130470196">"Aplikace %1$s použila mikrofon"</string> <string name="notification_vpn_connected" msgid="3891023882833274730">"Síť VPN je připojena"</string> <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Síť VPN je odpojena"</string> - <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Prostřednictvím aplikace <xliff:g id="VPN_APP">%1$s</xliff:g>"</string> + <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Přes <xliff:g id="VPN_APP">%1$s</xliff:g>"</string> <string name="tv_notification_panel_title" msgid="5311050946506276154">"Oznámení"</string> <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Žádná oznámení"</string> </resources> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index eb5f8caa2b78..e50aa57e70cb 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Vil du fjerne gæsten?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Fjern"</string> + <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 66fb7eb480fe..3cdebf9c80d9 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Gast entfernen?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Entfernen"</string> + <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 40e229426d2b..c6ae7f4b0c51 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Κατάργηση επισκέπτη;"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Λήξη περιόδου σύνδεσης επισκέπτη;"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Όλες οι εφαρμογές και τα δεδομένα αυτής της περιόδου σύνδεσης θα διαγραφούν."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Κατάργηση"</string> + <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 ca81ed6e7e17..ea25ec6e771c 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +455,9 @@ <string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Show profile"</string> <string name="user_add_user" msgid="4336657383006913022">"Add user"</string> <string name="user_new_user_name" msgid="2019166282704195789">"New user"</string> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Remove guest?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"End Guest session?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Remove"</string> + <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"End session"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"Welcome back, guest!"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"Do you want to continue your session?"</string> <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Start again"</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 89537e905720..a373a5c0061d 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +455,9 @@ <string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Show profile"</string> <string name="user_add_user" msgid="4336657383006913022">"Add user"</string> <string name="user_new_user_name" msgid="2019166282704195789">"New user"</string> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Remove guest?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"End Guest session?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Remove"</string> + <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"End session"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"Welcome back, guest!"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"Do you want to continue your session?"</string> <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Start again"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index ca81ed6e7e17..ea25ec6e771c 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +455,9 @@ <string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Show profile"</string> <string name="user_add_user" msgid="4336657383006913022">"Add user"</string> <string name="user_new_user_name" msgid="2019166282704195789">"New user"</string> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Remove guest?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"End Guest session?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Remove"</string> + <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"End session"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"Welcome back, guest!"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"Do you want to continue your session?"</string> <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Start again"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index ca81ed6e7e17..ea25ec6e771c 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +455,9 @@ <string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Show profile"</string> <string name="user_add_user" msgid="4336657383006913022">"Add user"</string> <string name="user_new_user_name" msgid="2019166282704195789">"New user"</string> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Remove guest?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"End Guest session?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Remove"</string> + <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"End session"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"Welcome back, guest!"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"Do you want to continue your session?"</string> <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Start again"</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 6fa2171b9041..101d11245ffc 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +455,9 @@ <string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Show profile"</string> <string name="user_add_user" msgid="4336657383006913022">"Add user"</string> <string name="user_new_user_name" msgid="2019166282704195789">"New user"</string> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Remove guest?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"End guest session?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Remove"</string> + <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"End session"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"Welcome back, guest!"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"Do you want to continue your session?"</string> <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Start over"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index e9a27854622a..1fc1609c7dcc 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +455,9 @@ <string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Mostrar perfil"</string> <string name="user_add_user" msgid="4336657383006913022">"Agregar usuario"</string> <string name="user_new_user_name" msgid="2019166282704195789">"Usuario nuevo"</string> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"¿Eliminar invitado?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"¿Quieres finalizar la sesión de invitado?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán las aplicaciones y los datos de esta sesión."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Eliminar"</string> + <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Finalizar sesión"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"Bienvenido nuevamente, invitado."</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"¿Quieres retomar la sesión?"</string> <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Volver a empezar"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 11ff9af1f7e7..dd36a64812ab 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -85,8 +85,7 @@ <string name="screenshot_failed_title" msgid="3259148215671936891">"No se ha podido guardar la captura de pantalla"</string> <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositivo debe desbloquearse para que se pueda guardar la captura de pantalla"</string> <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Vuelve a intentar hacer la captura de pantalla"</string> - <!-- no translation found for screenshot_failed_to_save_text (7232739948999195960) --> - <skip /> + <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No se puede guardar la captura de pantalla"</string> <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"La aplicación o tu organización no permiten realizar capturas de pantalla"</string> <string name="screenshot_edit_label" msgid="8754981973544133050">"Editar"</string> <string name="screenshot_edit_description" msgid="3333092254706788906">"Editar captura de pantalla"</string> @@ -354,6 +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> + <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> @@ -455,9 +455,9 @@ <string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Mostrar perfil"</string> <string name="user_add_user" msgid="4336657383006913022">"Añadir usuario"</string> <string name="user_new_user_name" msgid="2019166282704195789">"Nuevo usuario"</string> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"¿Quitar invitado?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"¿Finalizar sesión de invitado?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán todas las aplicaciones y datos de esta sesión."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Quitar"</string> + <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Finalizar sesión"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"Hola de nuevo, invitado"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"¿Quieres continuar con la sesión?"</string> <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Volver a empezar"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 27a9df054c4f..fb27be994265 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Kas eemaldada külaline?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Eemalda"</string> + <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 b2dc3d6361d2..d8b21c633dfa 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +455,9 @@ <string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Erakutsi profila"</string> <string name="user_add_user" msgid="4336657383006913022">"Gehitu erabiltzailea"</string> <string name="user_new_user_name" msgid="2019166282704195789">"Erabiltzaile berria"</string> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Gonbidatua kendu nahi duzu?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Gonbidatuentzako saioa amaitu nahi duzu?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Saioko aplikazio eta datu guztiak ezabatuko dira."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Kendu"</string> + <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Amaitu saioa"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"Ongi etorri berriro, gonbidatu hori!"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"Saioarekin jarraitu nahi duzu?"</string> <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Hasi berriro"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 214f5c2c616c..df306f83e16e 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"مهمان حذف شود؟"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"جلسه مهمان تمام شود؟"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"همه برنامهها و دادههای این جلسه حذف خواهد شد."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"حذف"</string> + <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 cb60ff4c4ebe..4e0af373f572 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Poistetaaanko vieras?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Poista"</string> + <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 17752c5d2c8a..8c944a4a0e0f 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Supprimer l\'invité?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Supprimer"</string> + <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 39d82ef44c6a..7ad239dc4ee2 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Supprimer l\'invité ?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Supprimer"</string> + <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 6494e0afc5e8..145b3c06b387 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Queres eliminar o invitado?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Eliminar"</string> + <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 369fd1d51f14..ea7af17dcb6a 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"અતિથિ દૂર કરીએ?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"શું અતિથિ સત્ર સમાપ્ત કરીએ?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"આ સત્રમાંની તમામ ઍપ્લિકેશનો અને ડેટા કાઢી નાખવામાં આવશે."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"દૂર કરો"</string> + <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-h700dp/dimens.xml b/packages/SystemUI/res/values-h700dp/dimens.xml new file mode 100644 index 000000000000..fbd985eaa751 --- /dev/null +++ b/packages/SystemUI/res/values-h700dp/dimens.xml @@ -0,0 +1,20 @@ +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<resources> + <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) --> + <dimen name="large_clock_text_size">170dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml index 6a0e880675df..cfacbec0dff2 100644 --- a/packages/SystemUI/res/values-h800dp/dimens.xml +++ b/packages/SystemUI/res/values-h800dp/dimens.xml @@ -17,4 +17,7 @@ <resources> <!-- Minimum margin between clock and top of screen or ambient indication --> <dimen name="keyguard_clock_top_margin">76dp</dimen> -</resources>
\ No newline at end of file + + <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) --> + <dimen name="large_clock_text_size">200dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 0b16f527e46a..a8bed5b26401 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -85,7 +85,7 @@ <string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रीनशॉट सेव नहीं किया जा सका"</string> <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"स्क्रीनशॉट सेव करने के लिए डिवाइस का अनलॉक होना ज़रूरी है"</string> <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रीनशॉट दोबारा लेने की कोशिश करें"</string> - <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रीनशॉट को सेव नहीं किया जा सका"</string> + <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रीनशॉट को सेव नहीं किया जा सकता"</string> <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"ऐप्लिकेशन या आपका संगठन स्क्रीनशॉट लेने की अनुमति नहीं देता"</string> <string name="screenshot_edit_label" msgid="8754981973544133050">"बदलाव करें"</string> <string name="screenshot_edit_description" msgid="3333092254706788906">"स्क्रीनशॉट में बदलाव करें"</string> @@ -355,6 +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> + <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,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"अतिथि को निकालें?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"मेहमान के तौर पर ब्राउज़ करने का सेशन खत्म करना चाहते हैं?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"इस सत्र के सभी ऐप्स और डेटा को हटा दिया जाएगा."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"निकालें"</string> + <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 2f13421590ae..4ffa5b2e04ff 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -354,6 +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> + <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> @@ -456,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Ukloniti gosta?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Ukloni"</string> + <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 a7b3cc315980..cff5b0dc65b3 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Eltávolítja a vendég munkamenetet?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Eltávolítás"</string> + <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-hu/strings_tv.xml b/packages/SystemUI/res/values-hu/strings_tv.xml index 78d6099b74a0..91183af142a5 100644 --- a/packages/SystemUI/res/values-hu/strings_tv.xml +++ b/packages/SystemUI/res/values-hu/strings_tv.xml @@ -23,7 +23,7 @@ <string name="app_accessed_mic" msgid="2754428675130470196">"A(z) %1$s hozzáfért a mikrofonhoz"</string> <string name="notification_vpn_connected" msgid="3891023882833274730">"A VPN-kapcsolat létrejött"</string> <string name="notification_vpn_disconnected" msgid="7150747626448044843">"A VPN-kapcsolat megszakadt"</string> - <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"A következő szolgáltatás használatával: <xliff:g id="VPN_APP">%1$s</xliff:g>"</string> + <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Ezzel: <xliff:g id="VPN_APP">%1$s</xliff:g>"</string> <string name="tv_notification_panel_title" msgid="5311050946506276154">"Értesítések"</string> <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Nincs értesítés"</string> </resources> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index fed73da0f120..91ffe7308a91 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Հեռացնե՞լ հյուրին:"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Ավարտե՞լ հյուրի աշխատաշրջանը"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Այս աշխատաշրջանի բոլոր ծրագրերն ու տվյալները կջնջվեն:"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Հեռացնել"</string> + <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-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 7ae3e5d7edb4..f9e7397993dc 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Hapus tamu?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Hapus"</string> + <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 0361ba4db718..db5dac43a42a 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Fjarlægja gest?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Fjarlægja"</string> + <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 948faf1ccf72..86e65bbc3629 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,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Rimuovere l\'ospite?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Rimuovi"</string> + <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 d20623e0efa1..8548dae09ee2 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -35,7 +35,7 @@ <string name="battery_low_why" msgid="2056750982959359863">"הגדרות"</string> <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"להפעיל את תכונת החיסכון בסוללה?"</string> <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"מידע על מצב החיסכון בסוללה"</string> - <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"הפעל"</string> + <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"הפעלה"</string> <string name="battery_saver_start_action" msgid="4553256017945469937">"הפעלת תכונת החיסכון בסוללה"</string> <string name="status_bar_settings_settings_button" msgid="534331565185171556">"הגדרות"</string> <string name="status_bar_settings_wifi_button" msgid="7243072479837270946">"Wi-Fi"</string> @@ -278,8 +278,8 @@ <string name="accessibility_quick_settings_flashlight_changed_on" msgid="4747870681508334200">"הפנס הופעל."</string> <string name="accessibility_quick_settings_color_inversion_changed_off" msgid="7548045840282925393">"היפוך צבעים כבוי."</string> <string name="accessibility_quick_settings_color_inversion_changed_on" msgid="4711141858364404084">"היפוך צבעים מופעל."</string> - <string name="accessibility_quick_settings_hotspot_changed_off" msgid="7002061268910095176">"נקודה לשיתוף אינטרנט בנייד כבויה."</string> - <string name="accessibility_quick_settings_hotspot_changed_on" msgid="2576895346762408840">"נקודה לשיתוף אינטרנט בנייד מופעלת."</string> + <string name="accessibility_quick_settings_hotspot_changed_off" msgid="7002061268910095176">"נקודת האינטרנט (hotspot) כבויה."</string> + <string name="accessibility_quick_settings_hotspot_changed_on" msgid="2576895346762408840">"נקודת האינטרנט (hotspot) מופעלת."</string> <string name="accessibility_casting_turned_off" msgid="1387906158563374962">"העברת המסך הופסקה."</string> <string name="accessibility_quick_settings_work_mode_off" msgid="562749867895549696">"מצב עבודה כבוי."</string> <string name="accessibility_quick_settings_work_mode_on" msgid="2779253456042059110">"מצב עבודה מופעל."</string> @@ -355,6 +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> + <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> @@ -377,7 +378,7 @@ <string name="quick_settings_connected_battery_level" msgid="1322075669498906959">"מחובר, הסוללה ב-<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="quick_settings_connecting" msgid="2381969772953268809">"מתחבר..."</string> <string name="quick_settings_tethering_label" msgid="5257299852322475780">"שיתוף אינטרנט בין ניידים"</string> - <string name="quick_settings_hotspot_label" msgid="1199196300038363424">"נקודה לשיתוף אינטרנט"</string> + <string name="quick_settings_hotspot_label" msgid="1199196300038363424">"נקודת אינטרנט (hotspot)"</string> <string name="quick_settings_hotspot_secondary_label_transient" msgid="7585604088079160564">"ההפעלה מתבצעת…"</string> <string name="quick_settings_hotspot_secondary_label_data_saver_enabled" msgid="1280433136266439372">"חוסך הנתונים פועל"</string> <plurals name="quick_settings_hotspot_secondary_label_num_devices" formatted="false" msgid="3142308865165871976"> @@ -458,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"להסיר אורח?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"להפסיק את הגלישה כאורח?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"כל האפליקציות והנתונים בפעילות זו באתר יימחקו."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"הסר"</string> + <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> @@ -649,7 +650,7 @@ <string name="alarm_template" msgid="2234991538018805736">"בשעה <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"ב-<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="accessibility_quick_settings_detail" msgid="544463655956179791">"הגדרות מהירות, <xliff:g id="TITLE">%s</xliff:g>."</string> - <string name="accessibility_status_bar_hotspot" msgid="2888479317489131669">"נקודה לשיתוף אינטרנט"</string> + <string name="accessibility_status_bar_hotspot" msgid="2888479317489131669">"נקודת אינטרנט (hotspot)"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"פרופיל עבודה"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"מהנה בשביל חלק מהאנשים, אבל לא בשביל כולם"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner מספק לך דרכים נוספות להתאים אישית את ממשק המשתמש של Android. התכונות הניסיוניות האלה עשויות להשתנות, להתקלקל או להיעלם בגרסאות עתידיות. המשך בזהירות."</string> @@ -666,7 +667,7 @@ <string name="experimental" msgid="3549865454812314826">"ניסיוני"</string> <string name="enable_bluetooth_title" msgid="866883307336662596">"האם להפעיל את ה-Bluetooth?"</string> <string name="enable_bluetooth_message" msgid="6740938333772779717">"כדי לחבר את המקלדת לטאבלט, תחילה עליך להפעיל את ה-Bluetooth."</string> - <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"הפעל"</string> + <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"הפעלה"</string> <string name="show_silently" msgid="5629369640872236299">"הצגת התראות בלי להשמיע צליל"</string> <string name="block" msgid="188483833983476566">"חסימת כל ההתראות"</string> <string name="do_not_silence" msgid="4982217934250511227">"לא להשתיק"</string> diff --git a/packages/SystemUI/res/values-iw/strings_tv.xml b/packages/SystemUI/res/values-iw/strings_tv.xml index 7483116177f6..1258f0d42aa0 100644 --- a/packages/SystemUI/res/values-iw/strings_tv.xml +++ b/packages/SystemUI/res/values-iw/strings_tv.xml @@ -21,8 +21,8 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="mic_active" msgid="5766614241012047024">"המיקרופון פעיל"</string> <string name="app_accessed_mic" msgid="2754428675130470196">"%1$s קיבלה גישה למיקרופון שלך"</string> - <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN מחובר"</string> - <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN מנותק"</string> + <string name="notification_vpn_connected" msgid="3891023882833274730">"ה-VPN מחובר"</string> + <string name="notification_vpn_disconnected" msgid="7150747626448044843">"ה-VPN מנותק"</string> <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"באמצעות <xliff:g id="VPN_APP">%1$s</xliff:g>"</string> <string name="tv_notification_panel_title" msgid="5311050946506276154">"התראות"</string> <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"אין התראות"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 3bb841d21694..6cd560854960 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"ゲストを削除しますか?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"ゲスト セッションを終了しますか?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"このセッションでのアプリとデータはすべて削除されます。"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"削除"</string> + <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 3e3405e23706..52d9f0e77451 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"სტუმრის ამოშლა?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"დასრულდეს სტუმრის სესია?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ამ სესიის ყველა აპი და მონაცემი წაიშლება."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"ამოშლა"</string> + <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 7cbb40b1ad34..fcf87434ad3b 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Қонақты жою керек пе?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Қонақ сеансы аяқталсын ба?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Осы сеанстағы барлық қолданбалар мен деректер жойылады."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Алып тастау"</string> + <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 e15cc9218269..e92bf9bda905 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"លុបភ្ញៀវ?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"បញ្ចប់វគ្គភ្ញៀវឬ?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ទិន្នន័យ និងកម្មវិធីទាំងអស់ក្នុងសម័យនេះនឹងត្រូវបានលុប។"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"លុបចេញ"</string> + <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 177db59697f5..d7a0adc85935 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"ಅತಿಥಿಯನ್ನು ತೆಗೆದುಹಾಕುವುದೇ?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"ಅತಿಥಿ ಸೆಷನ್ ಅಂತ್ಯಗೊಳಿಸುವುದೇ?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ಈ ಸೆಷನ್ನಲ್ಲಿನ ಎಲ್ಲ ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"ತೆಗೆದುಹಾಕಿ"</string> + <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 eb81396257a6..54bcfb978ac9 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"게스트를 삭제하시겠습니까?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"게스트 세션을 종료하시겠습니까?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"삭제"</string> + <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 caa2b31647c1..d513380f9355 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -355,6 +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> + <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,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Конокту алып саласызбы?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Конок сеансы бүтүрүлсүнбү?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана дайындар өчүрүлөт."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Алып салуу"</string> + <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 5ed1d4b0416f..7e595cd83176 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"ລຶບແຂກບໍ?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"ສິ້ນສຸດເຊດຊັນແຂກບໍ?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ແອັບຯແລະຂໍ້ມູນທັງໝົດໃນເຊດຊັນນີ້ຈະຖືກລຶບອອກ."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"ລຶບ"</string> + <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 17d517cda3de..e0c27a96de44 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -355,6 +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> + <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> @@ -458,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Pašalinti svečią?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Pašalinti"</string> + <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 6fd32b59f175..ff36f51b5603 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -354,6 +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> + <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> @@ -456,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Vai noņemt viesi?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Noņemt"</string> + <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 51f9b8c4d45a..203d8b90ae52 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Да се отстрани гостинот?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Да се заврши гостинската сесија?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Сите апликации и податоци во сесијата ќе се избришат."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Отстрани"</string> + <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 534faa719334..77925ae0c268 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"അതിഥിയെ നീക്കംചെയ്യണോ?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"അതിഥി സെഷൻ അവസാനിപ്പിക്കണോ?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ഈ സെഷനിലെ എല്ലാ അപ്ലിക്കേഷനുകളും ഡാറ്റയും ഇല്ലാതാക്കും."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"നീക്കംചെയ്യുക"</string> + <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 190e3e503798..c772294a6bdf 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Зочныг хасах уу?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Зочны сургалтыг дуусгах уу?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Энэ сешний бүх апп болон дата устах болно."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Хасах"</string> + <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-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 1f2e82e1c429..965b8958b458 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -85,8 +85,7 @@ <string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रीनशॉट सेव्ह करू शकलो नाही"</string> <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"स्क्रीनशॉट सेव्ह करण्याआधी डिव्हाइस अनलॉक करणे आवश्यक आहे"</string> <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रीनशॉट पुन्हा घेण्याचा प्रयत्न करा"</string> - <!-- no translation found for screenshot_failed_to_save_text (7232739948999195960) --> - <skip /> + <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रीनशॉट सेव्ह करू शकत नाही"</string> <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"अॅप किंवा आपल्या संस्थेद्वारे स्क्रीनशॉट घेण्याची अनुमती नाही"</string> <string name="screenshot_edit_label" msgid="8754981973544133050">"संपादित करा"</string> <string name="screenshot_edit_description" msgid="3333092254706788906">"स्क्रीनशॉट संपादित करा"</string> @@ -354,6 +353,8 @@ <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_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> @@ -455,9 +456,11 @@ <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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"अतिथी काढायचे?"</string> + <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) --> + <skip /> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"या सत्रातील सर्व अॅप्स आणि डेटा हटवला जाईल."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"काढा"</string> + <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) --> + <skip /> <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 17fe32d3b2d5..d3e360147d0f 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Alih keluar tetamu?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Alih keluar"</string> + <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 610105eb6b4d..0c1f9cbbc060 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"ဧည့်သည်ကို ဖယ်ထုတ်လိုက်ရမလား?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"ဧည့်သည်စက်ရှင်ကို အဆုံးသတ်မလား။"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ဒီချိတ်ဆက်မှု ထဲက အက်ပ်များ အားလုံး နှင့် ဒေတာကို ဖျက်ပစ်မည်။"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"ဖယ်ထုတ်ပါ"</string> + <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 b16f1e78af00..565e7fea215d 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Vil du fjerne gjesten?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Fjern"</string> + <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 fb4dca094a5c..9d95cf042dad 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -85,8 +85,7 @@ <string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string> <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"यन्त्र अनलक गरेपछि मात्र स्क्रिनसट सुरक्षित गर्न सकिन्छ"</string> <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रिनसट फेरि लिएर हेर्नुहोस्"</string> - <!-- no translation found for screenshot_failed_to_save_text (7232739948999195960) --> - <skip /> + <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string> <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"उक्त एप वा तपाईंको संगठनले स्क्रिनसटहरू लिन दिँदैन"</string> <string name="screenshot_edit_label" msgid="8754981973544133050">"सम्पादन गर्नुहोस्"</string> <string name="screenshot_edit_description" msgid="3333092254706788906">"स्क्रिनसट सम्पादन गर्नुहोस्"</string> @@ -354,6 +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> + <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> @@ -455,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"अतिथि हटाउने हो?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"अतिथिको सत्र अन्त्य गर्ने हो?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"यस सत्रमा सबै एपहरू र डेटा मेटाइनेछ।"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"हटाउनुहोस्"</string> + <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 a858a1d79e0e..f598fa3fae09 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Gast verwijderen?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Verwijderen"</string> + <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 4931b770cada..1e07dd4b785e 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -353,6 +353,8 @@ <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_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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"ଅତିଥିଙ୍କୁ କାଢ଼ିଦେବେ?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"ଅତିଥି ସେସନ୍ ଶେଷ କରିବେ?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ଏହି ଅବଧିର ସମସ୍ତ ଆପ୍ ଓ ଡାଟା ଡିଲିଟ୍ ହୋଇଯିବ।"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"କାଢ଼ିଦିଅନ୍ତୁ"</string> + <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-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index a2ff2b2932db..7d5bc96830a5 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -85,8 +85,7 @@ <string name="screenshot_failed_title" msgid="3259148215671936891">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string> <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਨੂੰ ਰੱਖਿਅਤ ਕੀਤੇ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਡੀਵਾਈਸ ਨੂੰ ਅਣਲਾਕ ਕੀਤਾ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ"</string> <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਦੁਬਾਰਾ ਲੈ ਕੇ ਦੇਖੋ"</string> - <!-- no translation found for screenshot_failed_to_save_text (7232739948999195960) --> - <skip /> + <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਨੂੰ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string> <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"ਐਪ ਜਾਂ ਤੁਹਾਡੀ ਸੰਸਥਾ ਵੱਲੋਂ ਸਕ੍ਰੀਨਸ਼ਾਟ ਲੈਣ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਦਿੱਤੀ ਗਈ ਹੈ"</string> <string name="screenshot_edit_label" msgid="8754981973544133050">"ਸੰਪਾਦਨ ਕਰੋ"</string> <string name="screenshot_edit_description" msgid="3333092254706788906">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string> @@ -354,6 +353,8 @@ <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_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> @@ -455,9 +456,11 @@ <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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"ਕੀ ਮਹਿਮਾਨ ਹਟਾਉਣਾ ਹੈ?"</string> + <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) --> + <skip /> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ਇਸ ਸੈਸ਼ਨ ਵਿੱਚ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟਾ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਏਗਾ।"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"ਹਟਾਓ"</string> + <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) --> + <skip /> <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 99552857a17b..561e70dd14cb 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -355,6 +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> + <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> @@ -458,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Usunąć gościa?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Usuń"</string> + <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 9b7220fb3c52..7fe53d348e15 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Remover convidado?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Remover"</string> + <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> @@ -836,7 +837,7 @@ <item msgid="7453955063378349599">"Inclinação à esquerda"</item> <item msgid="5874146774389433072">"Inclinação à direita"</item> </string-array> - <string name="menu_ime" msgid="5677467548258017952">"Alternador de teclado"</string> + <string name="menu_ime" msgid="5677467548258017952">"Seletor de teclado"</string> <string name="save" msgid="3392754183673848006">"Salvar"</string> <string name="reset" msgid="8715144064608810383">"Redefinir"</string> <string name="adjust_button_width" msgid="8313444823666482197">"Ajustar largura do botão"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 9b5267781ee9..dd154e8ad684 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Remover o convidado?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Remover"</string> + <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 9b7220fb3c52..7fe53d348e15 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Remover convidado?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Remover"</string> + <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> @@ -836,7 +837,7 @@ <item msgid="7453955063378349599">"Inclinação à esquerda"</item> <item msgid="5874146774389433072">"Inclinação à direita"</item> </string-array> - <string name="menu_ime" msgid="5677467548258017952">"Alternador de teclado"</string> + <string name="menu_ime" msgid="5677467548258017952">"Seletor de teclado"</string> <string name="save" msgid="3392754183673848006">"Salvar"</string> <string name="reset" msgid="8715144064608810383">"Redefinir"</string> <string name="adjust_button_width" msgid="8313444823666482197">"Ajustar largura do botão"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index d0d01036a041..317fc098d522 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -354,6 +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> + <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> @@ -456,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Ștergeți invitatul?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Ștergeți"</string> + <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 1b1aa29db67c..bbb014e82b07 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -355,6 +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> + <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,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Удалить аккаунт гостя?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Завершить гостевой сеанс?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Все приложения и данные этого профиля будут удалены."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Удалить"</string> + <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-ru/strings_tv.xml b/packages/SystemUI/res/values-ru/strings_tv.xml index e08ade3bc065..8ce0dc203923 100644 --- a/packages/SystemUI/res/values-ru/strings_tv.xml +++ b/packages/SystemUI/res/values-ru/strings_tv.xml @@ -23,7 +23,7 @@ <string name="app_accessed_mic" msgid="2754428675130470196">"Приложение \"%1$s\" использовало доступ к микрофону."</string> <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN-подключение установлено"</string> <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN-подключение отключено"</string> - <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Отправлено через <xliff:g id="VPN_APP">%1$s</xliff:g>"</string> + <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Через приложение <xliff:g id="VPN_APP">%1$s</xliff:g>"</string> <string name="tv_notification_panel_title" msgid="5311050946506276154">"Уведомления"</string> <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Уведомлений нет."</string> </resources> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 998161496f0c..a7a2bb7dbbff 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"අමුත්තාන් ඉවත් කරන්නද?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"ආරාධිත සැසිය අවසන් කරන්නද?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"මෙම සැසියේ සියළුම යෙදුම් සහ දත්ත මකාවී."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"ඉවත් කරන්න"</string> + <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 470b02154b55..7eb5297a43f1 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -355,6 +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> + <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> @@ -458,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Odstrániť hosťa?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Odstrániť"</string> + <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 08fdb5895d90..6d23f26a6c8d 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -355,6 +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> + <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> @@ -458,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Želite odstraniti gosta?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Odstrani"</string> + <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-sl/strings_tv.xml b/packages/SystemUI/res/values-sl/strings_tv.xml index 57d70c0c3ecb..1f66138c175a 100644 --- a/packages/SystemUI/res/values-sl/strings_tv.xml +++ b/packages/SystemUI/res/values-sl/strings_tv.xml @@ -21,8 +21,8 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="mic_active" msgid="5766614241012047024">"Mikrofon je aktiven"</string> <string name="app_accessed_mic" msgid="2754428675130470196">"Aplikacija %1$s je dostopala do mikrofona"</string> - <string name="notification_vpn_connected" msgid="3891023882833274730">"Povezava z navideznim zasebnim omrežjem je vzpostavljena"</string> - <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Povezava z navideznim zasebnim omrežjem je prekinjena"</string> + <string name="notification_vpn_connected" msgid="3891023882833274730">"Povezava z omrežjem VPN je vzpostavljena"</string> + <string name="notification_vpn_disconnected" msgid="7150747626448044843">"Povezava z omrežjem VPN je prekinjena"</string> <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Prek storitve <xliff:g id="VPN_APP">%1$s</xliff:g>"</string> <string name="tv_notification_panel_title" msgid="5311050946506276154">"Obvestila"</string> <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Ni obvestil"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index aaee22511860..7e89b93cec82 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -57,15 +57,15 @@ <string name="label_view" msgid="6815442985276363364">"Pamje"</string> <string name="always_use_device" msgid="210535878779644679">"Hap gjithmonë <xliff:g id="APPLICATION">%1$s</xliff:g> kur lidhet <xliff:g id="USB_DEVICE">%2$s</xliff:g>"</string> <string name="always_use_accessory" msgid="1977225429341838444">"Hap gjithmonë <xliff:g id="APPLICATION">%1$s</xliff:g> kur lidhet <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>"</string> - <string name="usb_debugging_title" msgid="8274884945238642726">"Të lejohet korrigjimi i USB-së?"</string> + <string name="usb_debugging_title" msgid="8274884945238642726">"Të lejohet korrigjimi përmes USB-së?"</string> <string name="usb_debugging_message" msgid="5794616114463921773">"Gjurma e gishtit të tastit \"RSA\" së kompjuterit është:\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> <string name="usb_debugging_always" msgid="4003121804294739548">"Lejo gjithmonë nga ky kompjuter"</string> <string name="usb_debugging_allow" msgid="1722643858015321328">"Lejo"</string> - <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Korrigjimi i USB-së nuk lejohet"</string> - <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Përdoruesi i identifikuar aktualisht në këtë pajisje nuk mund ta aktivizojë korrigjimin e USB-së. Për ta përdorur këtë funksion, kalo te përdoruesi parësor."</string> + <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Korrigjimi përmes USB-së nuk lejohet"</string> + <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Përdoruesi i identifikuar aktualisht në këtë pajisje nuk mund ta aktivizojë korrigjimin përmes USB-së. Për ta përdorur këtë veçori, kalo te përdoruesi parësor."</string> <string name="wifi_debugging_title" msgid="7300007687492186076">"Do ta lejosh korrigjimin përmes Wi-Fi në këtë rrjet?"</string> <string name="wifi_debugging_message" msgid="5461204211731802995">"Emri i rrjetit (SSID)\n<xliff:g id="SSID_0">%1$s</xliff:g>\n\nAdresa Wi‑Fi (BSSID)\n<xliff:g id="BSSID_1">%2$s</xliff:g>"</string> - <string name="wifi_debugging_always" msgid="2968383799517975155">"Shfaq gjithmonë në këtë rrjet"</string> + <string name="wifi_debugging_always" msgid="2968383799517975155">"Lejo gjithmonë në këtë rrjet"</string> <string name="wifi_debugging_allow" msgid="4573224609684957886">"Lejo"</string> <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Korrigjimi përmes Wi-Fi nuk lejohet"</string> <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Përdoruesi i identifikuar aktualisht në këtë pajisje nuk mund ta aktivizojë korrigjimin përmes Wi-Fi. Për ta përdorur këtë veçori, kalo te përdoruesi parësor."</string> @@ -353,6 +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> + <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> @@ -454,9 +455,9 @@ <string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Shfaq profilin"</string> <string name="user_add_user" msgid="4336657383006913022">"Shto përdorues"</string> <string name="user_new_user_name" msgid="2019166282704195789">"Përdorues i ri"</string> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Të hiqet i ftuari?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Dëshiron t\'i japësh fund sesionit të vizitorit?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Të gjitha aplikacionet dhe të dhënat në këtë sesion do të fshihen."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Hiq"</string> + <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Jepi fund sesionit"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"Mirë se erdhe, i ftuar!"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"Dëshiron ta vazhdosh sesionin tënd?"</string> <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Fillo nga e para"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index d7645be711ed..af5175c2bbf6 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -354,6 +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> + <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,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Желите ли да уклоните госта?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Желите да завршите сесију госта?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Све апликације и подаци у овој сесији ће бити избрисани."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Уклони"</string> + <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 8e5e79e809f5..41bca7c90b32 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Vill du ta bort gästen?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Ta bort"</string> + <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-sv/strings_tv.xml b/packages/SystemUI/res/values-sv/strings_tv.xml index d7261e6b1445..fd8fa4b1bb8e 100644 --- a/packages/SystemUI/res/values-sv/strings_tv.xml +++ b/packages/SystemUI/res/values-sv/strings_tv.xml @@ -23,7 +23,7 @@ <string name="app_accessed_mic" msgid="2754428675130470196">"%1$s har fått åtkomst till mikrofonen"</string> <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN är anslutet"</string> <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN är frånkopplat"</string> - <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string> + <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string> <string name="tv_notification_panel_title" msgid="5311050946506276154">"Aviseringar"</string> <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Inga aviseringar"</string> </resources> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 768fd8549d48..f122013cc150 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Ungependa kumwondoa mgeni?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Ondoa"</string> + <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 efdace48ff8e..c076a031b93f 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"கெஸ்ட்டை அகற்றவா?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"விருந்தினர் அமர்வை நிறைவுசெய்யவா?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"இந்த அமர்வின் எல்லா பயன்பாடுகளும், தரவும் நீக்கப்படும்."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"அகற்று"</string> + <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 36a5de721451..fca51077d06f 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"అతిథిని తీసివేయాలా?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"గెస్ట్ సెషన్ను ముగించాలా?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్లోని అన్ని అనువర్తనాలు మరియు డేటా తొలగించబడతాయి."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"తీసివేయి"</string> + <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 5dd4400e9ece..c909d379dedc 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -93,7 +93,7 @@ <string name="screenshot_scroll_description" msgid="7855773867093272175">"เลื่อนจับภาพหน้าจอ"</string> <string name="screenshot_dismiss_description" msgid="4702341245899508786">"ปิดภาพหน้าจอ"</string> <string name="screenshot_preview_description" msgid="7606510140714080474">"ตัวอย่างภาพหน้าจอ"</string> - <string name="screenrecord_name" msgid="2596401223859996572">"โปรแกรมอัดหน้าจอ"</string> + <string name="screenrecord_name" msgid="2596401223859996572">"โปรแกรมบันทึกหน้าจอ"</string> <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"กำลังประมวลผลการอัดหน้าจอ"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"การแจ้งเตือนต่อเนื่องสำหรับเซสชันการบันทึกหน้าจอ"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"เริ่มบันทึกเลยไหม"</string> @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"ต้องการนำผู้เข้าร่วมออกไหม"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"จบเซสชันผู้เยี่ยมชมใช่ไหม"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ระบบจะลบแอปและข้อมูลทั้งหมดในเซสชันนี้"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"นำออก"</string> + <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> @@ -744,7 +745,7 @@ <string name="notification_conversation_home_screen" msgid="8347136037958438935">"เพิ่มลงในหน้าจอหลัก"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"ส่วนควบคุมการแจ้งเตือน"</string> - <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ตัวเลือกการปิดเสียงแจ้งเตือนชั่วคราว"</string> + <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ตัวเลือกการเลื่อนการแจ้งเตือน"</string> <string name="notification_menu_snooze_action" msgid="5415729610393475019">"เตือนฉัน"</string> <string name="notification_menu_settings_action" msgid="7085494017202764285">"การตั้งค่า"</string> <string name="snooze_undo" msgid="60890935148417175">"เลิกทำ"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 0ade0ed7416d..2e75fa3de905 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Alisin ang bisita?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Alisin"</string> + <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 2608931f296e..98552b1b135d 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Misafir oturumu kaldırılsın mı?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Kaldır"</string> + <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-tr/strings_tv.xml b/packages/SystemUI/res/values-tr/strings_tv.xml index babd460a1375..49e76af004ab 100644 --- a/packages/SystemUI/res/values-tr/strings_tv.xml +++ b/packages/SystemUI/res/values-tr/strings_tv.xml @@ -23,7 +23,7 @@ <string name="app_accessed_mic" msgid="2754428675130470196">"%1$s mikrofonunuza erişti"</string> <string name="notification_vpn_connected" msgid="3891023882833274730">"VPN bağlandı"</string> <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN bağlantısı kesildi"</string> - <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> yoluyla"</string> + <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> üzerinden"</string> <string name="tv_notification_panel_title" msgid="5311050946506276154">"Bildirimler"</string> <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"Bildirim Yok"</string> </resources> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index ce00b92093ef..b70d36af64c2 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -355,6 +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> + <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,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Вийти з режиму гостя?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Завершити сеанс у режимі \"Гість\"?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усі додатки й дані з цього сеансу буде видалено."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Вийти"</string> + <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 0032c6bd7c74..bbadf6a7d5f1 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"مہمان کو ہٹائیں؟"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"مہمان سیشن ختم کریں؟"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"اس سیشن میں موجود سبھی ایپس اور ڈیٹا کو حذف کر دیا جائے گا۔"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"ہٹائیں"</string> + <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 532fa4021131..a20676dca577 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +455,9 @@ <string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Profilni ko‘rsatish"</string> <string name="user_add_user" msgid="4336657383006913022">"Foydalanuvchi"</string> <string name="user_new_user_name" msgid="2019166282704195789">"Yangi foydalanuvchi"</string> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Mehmon hisobi o‘chirib tashlansinmi?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Mehmon seansi yakunlansinmi?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ushbu seansdagi barcha ilovalar va ma’lumotlar o‘chirib tashlanadi."</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Olib tashlash"</string> + <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Seansni yakunlash"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"Xush kelibsiz, mehmon!"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"Seansni davom ettirmoqchimisiz?"</string> <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Boshidan boshlansin"</string> @@ -493,7 +494,7 @@ <string name="notification_section_header_alerting" msgid="5581175033680477651">"Bildirishnomalar"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Suhbatlar"</string> <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Barcha sokin bildirishnomalarni tozalash"</string> - <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Bezovta qilinmasin rejimida bildirishnomalar pauza qilingan"</string> + <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Bezovta qilinmasin rejimida bildirishnomalar pauza qilinadi"</string> <string name="media_projection_action_text" msgid="3634906766918186440">"Boshlash"</string> <string name="empty_shade_text" msgid="8935967157319717412">"Bildirishnomalar yo‘q"</string> <string name="profile_owned_footer" msgid="2756770645766113964">"Profil kuzatilishi mumkin"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 0e9987732d26..5de7df6a5d69 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Xóa phiên khách?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Xóa"</string> + <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 07232ff1119e..7f3f3943e3c5 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"要移除访客吗?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"要结束访客会话吗?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"此会话中的所有应用和数据都将被删除。"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"移除"</string> + <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 7a07af104f07..485f1cf786be 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"移除訪客?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"要結束訪客工作階段嗎?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會被刪除。"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"移除"</string> + <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 0369d6318760..1edeacd1a843 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"移除訪客?"</string> + <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"要結束訪客工作階段嗎?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會遭到刪除。"</string> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"移除"</string> + <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 068e2e53f8a1..58baebc27cc6 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -353,6 +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> + <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> @@ -454,9 +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> - <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Susa isivakashi?"</string> + <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> - <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Susa"</string> + <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 897e3902b55c..4059b49ec486 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 6f69483106e5..72dd72410af3 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> @@ -1041,6 +1042,13 @@ burn-in on AOD. --> <dimen name="burn_in_prevention_offset_y">50dp</dimen> + <!-- The maximum offset in either direction that elements are moved vertically to prevent + burn-in on AOD. --> + <dimen name="burn_in_prevention_offset_y_large_clock">42dp</dimen> + + <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) --> + <dimen name="large_clock_text_size">150dp</dimen> + <!-- The maximum offset in either direction that icons move to prevent burn-in on AOD. --> <dimen name="default_burn_in_prevention_offset">15dp</dimen> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 323449af0850..2d972e04bd1d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -1,6 +1,7 @@ package com.android.keyguard; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; @@ -23,6 +24,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.RelativeLayout; import android.widget.TextClock; +import android.widget.TextView; import com.android.internal.colorextraction.ColorExtractor; import com.android.keyguard.dagger.KeyguardStatusViewScope; @@ -107,7 +109,10 @@ public class KeyguardClockSwitch extends RelativeLayout { /** * Boolean value indicating if notifications are visible on lock screen. */ - private boolean mHasVisibleNotifications; + private boolean mHasVisibleNotifications = true; + + private AnimatorSet mClockInAnim = null; + private AnimatorSet mClockOutAnim = null; /** * If the Keyguard Slice has a header (big center-aligned text.) @@ -143,6 +148,10 @@ public class KeyguardClockSwitch extends RelativeLayout { setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources() .getDimensionPixelSize(R.dimen.widget_big_font_size)); + ((TextView) mNewLockscreenLargeClockFrame.getChildAt(0)) + .setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources() + .getDimensionPixelSize(R.dimen.large_clock_text_size)); + mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize( R.dimen.keyguard_clock_switch_y_shift); } @@ -318,12 +327,15 @@ public class KeyguardClockSwitch extends RelativeLayout { private void animateClockChange(boolean useLargeClock) { if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) return; + if (mClockInAnim != null) mClockInAnim.cancel(); + if (mClockOutAnim != null) mClockOutAnim.cancel(); + View in, out; int direction = 1; if (useLargeClock) { out = mNewLockscreenClockFrame; in = mNewLockscreenLargeClockFrame; - addView(in); + if (indexOfChild(in) == -1) addView(in); direction = -1; } else { in = mNewLockscreenClockFrame; @@ -333,25 +345,35 @@ public class KeyguardClockSwitch extends RelativeLayout { removeView(out); } - AnimatorSet outAnim = new AnimatorSet(); - outAnim.setDuration(CLOCK_OUT_MILLIS); - outAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); - outAnim.playTogether( + mClockOutAnim = new AnimatorSet(); + mClockOutAnim.setDuration(CLOCK_OUT_MILLIS); + mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); + mClockOutAnim.playTogether( ObjectAnimator.ofFloat(out, View.ALPHA, 0f), ObjectAnimator.ofFloat(out, View.TRANSLATION_Y, 0, direction * -mClockSwitchYAmount)); + mClockOutAnim.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + mClockOutAnim = null; + } + }); in.setAlpha(0); in.setVisibility(View.VISIBLE); - AnimatorSet inAnim = new AnimatorSet(); - inAnim.setDuration(CLOCK_IN_MILLIS); - inAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - inAnim.playTogether(ObjectAnimator.ofFloat(in, View.ALPHA, 1f), + mClockInAnim = new AnimatorSet(); + mClockInAnim.setDuration(CLOCK_IN_MILLIS); + mClockInAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + mClockInAnim.playTogether(ObjectAnimator.ofFloat(in, View.ALPHA, 1f), ObjectAnimator.ofFloat(in, View.TRANSLATION_Y, direction * mClockSwitchYAmount, 0)); - inAnim.setStartDelay(CLOCK_OUT_MILLIS / 2); + mClockInAnim.setStartDelay(CLOCK_OUT_MILLIS / 2); + mClockInAnim.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + mClockInAnim = null; + } + }); - inAnim.start(); - outAnim.start(); + mClockInAnim.start(); + mClockOutAnim.start(); } /** @@ -374,7 +396,6 @@ public class KeyguardClockSwitch extends RelativeLayout { if (hasVisibleNotifications == mHasVisibleNotifications) { return; } - animateClockChange(!hasVisibleNotifications); mHasVisibleNotifications = hasVisibleNotifications; diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 08262defbdee..eefae5b586ee 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -111,7 +111,8 @@ public class SystemUIFactory { .setBubbles(mWMComponent.getBubbles()) .setHideDisplayCutout(mWMComponent.getHideDisplayCutout()) .setShellCommandHandler(mWMComponent.getShellCommandHandler()) - .setAppPairs(mWMComponent.getAppPairs()); + .setAppPairs(mWMComponent.getAppPairs()) + .setTaskViewFactory(mWMComponent.getTaskViewFactory()); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option // is separating this logic into newly creating SystemUITestsFactory. @@ -122,7 +123,8 @@ public class SystemUIFactory { .setBubbles(Optional.ofNullable(null)) .setHideDisplayCutout(Optional.ofNullable(null)) .setShellCommandHandler(Optional.ofNullable(null)) - .setAppPairs(Optional.ofNullable(null)); + .setAppPairs(Optional.ofNullable(null)) + .setTaskViewFactory(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); if (initializeComponents) { diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index c289ca2173be..1036c9916c8b 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 @@ package com.android.systemui.appops; 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.media.AudioRecordingConfiguration; 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 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon 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 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mAudioManager = audioManager; mMicMuted = audioManager.isMicrophoneMute(); mLocationManager = context.getSystemService(LocationManager.class); + mPackageManager = context.getPackageManager(); dumpManager.registerDumpable(TAG, this); } @@ -334,6 +339,16 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon 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 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon || 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/controls/ui/ChallengeDialogs.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt index 6c28d11df655..ff55b76d4db7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt @@ -26,7 +26,9 @@ import android.service.controls.actions.FloatAction import android.service.controls.actions.ModeAction import android.text.InputType import android.util.Log +import android.view.LayoutInflater import android.view.WindowManager +import android.view.inputmethod.InputMethodManager import android.widget.CheckBox import android.widget.EditText @@ -71,11 +73,21 @@ object ChallengeDialogs { R.string.controls_pin_instructions ) } - val builder = AlertDialog.Builder(cvh.context, STYLE).apply { + return object : AlertDialog(cvh.context, STYLE) { + override fun dismiss() { + window?.decorView?.let { + // workaround for b/159309083 + it.context.getSystemService(InputMethodManager::class.java) + ?.hideSoftInputFromWindow(it.windowToken, 0) + } + super.dismiss() + } + }.apply { setTitle(title) - setView(R.layout.controls_dialog_pin) - setPositiveButton( - android.R.string.ok, + setView(LayoutInflater.from(context).inflate(R.layout.controls_dialog_pin, null)) + setButton( + DialogInterface.BUTTON_POSITIVE, + context.getText(android.R.string.ok), DialogInterface.OnClickListener { dialog, _ -> if (dialog is Dialog) { dialog.requireViewById<EditText>(R.id.controls_pin_input) @@ -85,15 +97,15 @@ object ChallengeDialogs { dialog.dismiss() } }) - setNegativeButton( - android.R.string.cancel, + setButton( + DialogInterface.BUTTON_NEGATIVE, + context.getText(android.R.string.cancel), DialogInterface.OnClickListener { dialog, _ -> onCancel.invoke() dialog.cancel() } ) - } - return builder.create().apply { + getWindow().apply { setType(WINDOW_TYPE) setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index ab8222547597..7cd7e18965c2 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -36,6 +36,8 @@ import com.android.systemui.globalactions.GlobalActionsComponent import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.wm.shell.TaskViewFactory +import java.util.Optional import javax.inject.Inject @SysUISingleton @@ -45,7 +47,8 @@ class ControlActionCoordinatorImpl @Inject constructor( @Main private val uiExecutor: DelayableExecutor, private val activityStarter: ActivityStarter, private val keyguardStateController: KeyguardStateController, - private val globalActionsComponent: GlobalActionsComponent + private val globalActionsComponent: GlobalActionsComponent, + private val taskViewFactory: Optional<TaskViewFactory> ) : ControlActionCoordinator { private var dialog: Dialog? = null private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator @@ -163,11 +166,13 @@ class ControlActionCoordinatorImpl @Inject constructor( uiExecutor.execute { // make sure the intent is valid before attempting to open the dialog - if (activities.isNotEmpty()) { - dialog = DetailDialog(cvh, intent).also { - it.setOnDismissListener { _ -> dialog = null } - it.show() - } + if (activities.isNotEmpty() && taskViewFactory.isPresent) { + taskViewFactory.get().create(cvh.context, uiExecutor, { + dialog = DetailDialog(cvh, it, intent).also { + it.setOnDismissListener { _ -> dialog = null } + it.show() + } + }) } else { cvh.setErrorStatus() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index 5b11627bbc3b..e4f906486f8c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -16,8 +16,12 @@ package com.android.systemui.controls.ui -import android.app.ActivityView +import android.app.ActivityOptions +import android.app.ActivityTaskManager +import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.Dialog +import android.app.PendingIntent +import android.content.ComponentName import android.content.Intent import android.provider.Settings import android.view.View @@ -26,9 +30,9 @@ import android.view.WindowInsets import android.view.WindowInsets.Type import android.view.WindowManager import android.widget.ImageView - import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.R +import com.android.wm.shell.TaskView /** * A dialog that provides an {@link ActivityView}, allowing the application to provide @@ -37,6 +41,7 @@ import com.android.systemui.R */ class DetailDialog( val cvh: ControlViewHolder, + val activityView: TaskView, val intent: Intent ) : Dialog(cvh.context, R.style.Theme_SystemUI_Dialog_Control_DetailPanel) { @@ -49,10 +54,16 @@ class DetailDialog( private const val EXTRA_USE_PANEL = "controls.DISPLAY_IN_PANEL" } - var activityView = ActivityView(context) + var detailTaskId = INVALID_TASK_ID + + fun removeDetailTask() { + if (detailTaskId == INVALID_TASK_ID) return + ActivityTaskManager.getInstance().removeTask(detailTaskId) + detailTaskId = INVALID_TASK_ID + } - val stateCallback: ActivityView.StateCallback = object : ActivityView.StateCallback() { - override fun onActivityViewReady(view: ActivityView) { + val stateCallback = object : TaskView.Listener { + override fun onInitialized() { val launchIntent = Intent(intent) launchIntent.putExtra(EXTRA_USE_PANEL, true) @@ -60,18 +71,31 @@ class DetailDialog( launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) - view.startActivity(launchIntent) + activityView.startActivity( + PendingIntent.getActivity(context, 0, launchIntent, + PendingIntent.FLAG_UPDATE_CURRENT), null, ActivityOptions.makeBasic()) } - override fun onActivityViewDestroyed(view: ActivityView) {} - override fun onTaskRemovalStarted(taskId: Int) { + detailTaskId = INVALID_TASK_ID dismiss() } + + override fun onTaskCreated(taskId: Int, name: ComponentName?) { + detailTaskId = taskId + } + + override fun onReleased() { + removeDetailTask() + } } init { window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) + // To pass touches to the task inside TaskView. + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL) + window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) + setContentView(R.layout.controls_detail_dialog) requireViewById<ViewGroup>(R.id.controls_activity_view).apply { @@ -84,6 +108,9 @@ class DetailDialog( requireViewById<ImageView>(R.id.control_detail_open_in_app).apply { setOnClickListener { v: View -> + // Remove the task explicitly, since onRelease() callback will be executed after + // startActivity() below is called. + removeDetailTask() dismiss() context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) v.context.startActivity(intent) @@ -126,7 +153,7 @@ class DetailDialog( } override fun show() { - activityView.setCallback(stateCallback) + activityView.setListener(cvh.uiExecutor, stateCallback) super.show() } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 926062b1623f..612a55970f02 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -25,12 +25,13 @@ import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.InjectionInflationController; import com.android.wm.shell.ShellCommandHandler; +import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import java.util.Optional; @@ -71,6 +72,9 @@ public interface SysUIComponent { Builder setBubbles(Optional<Bubbles> b); @BindsInstance + Builder setTaskViewFactory(Optional<TaskViewFactory> t); + + @BindsInstance Builder setHideDisplayCutout(Optional<HideDisplayCutout> h); @BindsInstance diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index a1bde589d5c1..c75dc84f8570 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -19,12 +19,13 @@ package com.android.systemui.dagger; import com.android.systemui.wmshell.WMShellModule; import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.ShellInit; +import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import java.util.Optional; @@ -79,4 +80,7 @@ public interface WMComponent { @WMSingleton Optional<HideDisplayCutout> getHideDisplayCutout(); + + @WMSingleton + Optional<TaskViewFactory> getTaskViewFactory(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt index 5c1c60c5b07e..80d13715ca07 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -29,11 +29,15 @@ import android.provider.Settings import android.service.media.MediaBrowserService import android.util.Log import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.Dumpable import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dump.DumpManager import com.android.systemui.tuner.TunerService import com.android.systemui.util.Utils +import java.io.FileDescriptor +import java.io.PrintWriter import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.Executor import javax.inject.Inject @@ -49,8 +53,9 @@ class MediaResumeListener @Inject constructor( private val broadcastDispatcher: BroadcastDispatcher, @Background private val backgroundExecutor: Executor, private val tunerService: TunerService, - private val mediaBrowserFactory: ResumeMediaBrowserFactory -) : MediaDataManager.Listener { + private val mediaBrowserFactory: ResumeMediaBrowserFactory, + dumpManager: DumpManager +) : MediaDataManager.Listener, Dumpable { private var useMediaResumption: Boolean = Utils.useMediaResumption(context) private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue() @@ -99,6 +104,7 @@ class MediaResumeListener @Inject constructor( init { if (useMediaResumption) { + dumpManager.registerDumpable(TAG, this) val unlockFilter = IntentFilter() unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED) unlockFilter.addAction(Intent.ACTION_USER_SWITCHED) @@ -283,4 +289,10 @@ class MediaResumeListener @Inject constructor( mediaBrowser?.restart() } } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + pw.apply { + println("resumeComponents: $resumeComponents") + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 4f8519289a07..5dd2f065cd4f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -46,7 +46,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.InfoMediaManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; -import com.android.settingslib.media.MediaOutputSliceConstants; +import com.android.settingslib.media.MediaOutputConstants; import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; @@ -449,8 +449,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { mCallback.dismissDialog(); final ActivityStarter.OnDismissAction postKeyguardAction = () -> { mContext.sendBroadcast(new Intent() - .setAction(MediaOutputSliceConstants.ACTION_LAUNCH_BLUETOOTH_PAIRING) - .setPackage(MediaOutputSliceConstants.SETTINGS_PACKAGE_NAME)); + .setAction(MediaOutputConstants.ACTION_LAUNCH_BLUETOOTH_PAIRING) + .setPackage(MediaOutputConstants.SETTINGS_PACKAGE_NAME)); mShadeController.animateCollapsePanels(); return true; }; diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt index 0ce0c020ccde..bd3f5a6d82a5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt @@ -20,7 +20,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.text.TextUtils -import com.android.settingslib.media.MediaOutputSliceConstants +import com.android.settingslib.media.MediaOutputConstants import javax.inject.Inject /** @@ -30,10 +30,10 @@ class MediaOutputDialogReceiver @Inject constructor( private val mediaOutputDialogFactory: MediaOutputDialogFactory ) : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - if (TextUtils.equals(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG, + if (TextUtils.equals(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG, intent.action)) { mediaOutputDialogFactory.create( - intent.getStringExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME), false) + intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME), false) } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java index 4efe4d85156b..f0e4cce299ee 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java @@ -461,14 +461,8 @@ public class KeyButtonView extends ImageView implements ButtonInterface { @Override public void draw(Canvas canvas) { if (mHasOvalBg) { - canvas.save(); - int cx = (getLeft() + getRight()) / 2; - int cy = (getTop() + getBottom()) / 2; - canvas.translate(cx, cy); int d = Math.min(getWidth(), getHeight()); - int r = d / 2; - canvas.drawOval(-r, -r, r, r, mOvalBgPaint); - canvas.restore(); + canvas.drawOval(0, 0, d, d, mOvalBgPaint); } super.draw(canvas); } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index 2ad12b9afdde..065920c9778b 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -22,7 +22,6 @@ import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID; import android.app.Activity; import android.app.INotificationManager; import android.app.people.IPeopleManager; -import android.app.people.PeopleSpaceTile; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTile.java index 95739f3dbf91..d7ee97bc3bc5 100644 --- a/core/java/android/app/people/PeopleSpaceTile.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTile.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.app.people; +package com.android.systemui.people; import android.annotation.NonNull; import android.app.Person; diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java index 9ae7847031aa..25b91da7fadc 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java @@ -16,7 +16,6 @@ package com.android.systemui.people; -import android.app.people.PeopleSpaceTile; import android.content.Context; import android.content.pm.LauncherApps; import android.graphics.drawable.Drawable; diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index f1a57bf4e2e7..12d5bac216e2 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -23,7 +23,7 @@ import android.app.Notification; import android.app.PendingIntent; import android.app.people.ConversationChannel; import android.app.people.IPeopleManager; -import android.app.people.PeopleSpaceTile; +import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.Intent; diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java index fb33affcbac5..cd0a5df2025a 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java @@ -18,7 +18,7 @@ package com.android.systemui.people.widget; import android.app.INotificationManager; import android.app.people.IPeopleManager; -import android.app.people.PeopleSpaceTile; +import com.android.systemui.people.PeopleSpaceTile; import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java index 0053fea35262..e822dd58fb98 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 @@ public class PageIndicator extends ViewGroup { } // 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 321f73295e2e..eaf212362320 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.animation.PropertyValuesHolder; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Rect; import android.os.Bundle; import android.util.AttributeSet; @@ -47,7 +48,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { }; 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } // 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } private void updateListening() { - for (TilePage tilePage : mPages) { + for (TileLayout tilePage : mPages) { tilePage.setListening(tilePage.getParent() != null && mListening); } } @@ -222,13 +229,14 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { @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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { 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 class PagedTileLayout extends ViewPager implements QSTileLayout { 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { 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 65f174c508e8..5eba147ab279 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.content.res.Resources; 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 @@ public class QSPanel extends LinearLayout implements Tunable { 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 @@ public class QSPanel extends LinearLayout implements Tunable { 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 class QSPanel extends LinearLayout implements Tunable { /** */ 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 @@ public class QSPanel extends LinearLayout implements Tunable { 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 @@ public class QSPanel extends LinearLayout implements Tunable { 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 cca0e1b0c0f0..e2d7d201a5de 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 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @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 000000000000..74a7ac1cb6dd --- /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 e38c931287b1..911261a01143 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 @@ public class TileLayout extends ViewGroup implements QSTileLayout { 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 @@ public class TileLayout extends ViewGroup implements QSTileLayout { return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); } - private int getCellHeight() { + protected int getCellHeight() { return mShowLabels ? mMaxCellHeight : mMaxCellHeight / 2; } @@ -260,4 +260,18 @@ public class TileLayout extends ViewGroup implements QSTileLayout { 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 e9d481b2e7f0..ba71fa6a8fb3 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.UserTile; 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 @@ public class QSFactoryImpl implements QSFactory { private final Lazy<QSHost> mQsHostLazy; private final Provider<CustomTile.Builder> mCustomTileBuilderProvider; + private final boolean mSideLabels; + @Inject public QSFactoryImpl( Lazy<QSHost> qsHostLazy, + SecureSettings settings, Provider<CustomTile.Builder> customTileBuilderProvider, Provider<WifiTile> wifiTileProvider, Provider<InternetTile> internetTileProvider, @@ -115,6 +119,8 @@ public class QSFactoryImpl implements QSFactory { mQsHostLazy = qsHostLazy; mCustomTileBuilderProvider = customTileBuilderProvider; + mSideLabels = settings.getInt("sysui_side_labels", 0) != 0; + mWifiTileProvider = wifiTileProvider; mInternetTileProvider = internetTileProvider; mBluetoothTileProvider = bluetoothTileProvider; @@ -218,6 +224,8 @@ public class QSFactoryImpl implements QSFactory { 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 655e4e2684e4..38e2ba4df79a 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 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { 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 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } } - 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 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { 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 650206672c1e..2dbd2cfe9c10 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 @@ import java.util.Objects; /** 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 @@ public class QSTileView extends QSTileBaseView { 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 @@ public class QSTileView extends QSTileBaseView { 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 @@ public class QSTileView extends QSTileBaseView { 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 @@ public class QSTileView extends QSTileBaseView { 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 @@ public class QSTileView extends QSTileBaseView { 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 000000000000..2ef78c249246 --- /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 4d89dea7cb70..19eac77136c6 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.AccessPointContro 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 @@ public class WifiTile extends QSTileImpl<SignalState> { 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 @@ public class WifiTile extends QSTileImpl<SignalState> { NetworkController.AccessPointController.AccessPointCallback, QSDetailItems.Callback { private QSDetailItems mItems; - private AccessPoint[] mAccessPoints; + private WifiEntry[] mAccessPoints; @Override public CharSequence getTitle() { @@ -366,8 +368,8 @@ public class WifiTile extends QSTileImpl<SignalState> { } @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 @@ public class WifiTile extends QSTileImpl<SignalState> { /** 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 @@ public class WifiTile extends QSTileImpl<SignalState> { @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 @@ public class WifiTile extends QSTileImpl<SignalState> { 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 @@ public class WifiTile extends QSTileImpl<SignalState> { 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/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 6d9d587bc729..54e30af675ab 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -91,11 +91,11 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedEvents; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index c2ba3440f19f..aa8d710e7570 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 @@ public class ScreenPinningRequest implements View.OnClickListener, } } - 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/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java index 2719d3e14d33..f77431a7047e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java @@ -306,10 +306,6 @@ class ImageExporter { exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTime); exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, subSec); exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, timeZone); - - exif.setAttribute(ExifInterface.TAG_DATETIME_DIGITIZED, dateTime); - exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, subSec); - exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED, timeZone); } static String getMimeType(CompressFormat format) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java new file mode 100644 index 000000000000..143121af9f2c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.screenshot; + +import static android.graphics.ColorSpace.Named.SRGB; + +import static java.util.Objects.requireNonNull; + +import android.graphics.Bitmap; +import android.graphics.ColorSpace; +import android.graphics.RecordingCanvas; +import android.graphics.Rect; +import android.graphics.RenderNode; +import android.media.Image; + +/** + * Holds a hardware image, coordinates and render node to draw the tile. The tile manages clipping + * and dimensions. The tile must be drawn translated to the correct target position: + * <pre> + * ImageTile tile = getTile(); + * canvas.save(); + * canvas.translate(tile.getLeft(), tile.getTop()); + * canvas.drawRenderNode(tile.getDisplayList()); + * canvas.restore(); + * </pre> + */ +class ImageTile implements AutoCloseable { + private final Image mImage; + private final Rect mLocation; + private RenderNode mNode; + + private static final ColorSpace COLOR_SPACE = ColorSpace.get(SRGB); + + /** + * Create an image tile from the given image. + * + * @param image an image containing a hardware buffer + * @param location the captured area represented by image tile (virtual coordinates) + */ + ImageTile(Image image, Rect location) { + mImage = requireNonNull(image, "image"); + mLocation = location; + + requireNonNull(mImage.getHardwareBuffer(), "image must be a hardware image"); + } + + RenderNode getDisplayList() { + if (mNode == null) { + mNode = new RenderNode("Tile{" + Integer.toHexString(mImage.hashCode()) + "}"); + } + if (mNode.hasDisplayList()) { + return mNode; + } + final int w = Math.min(mImage.getWidth(), mLocation.width()); + final int h = Math.min(mImage.getHeight(), mLocation.height()); + mNode.setPosition(0, 0, w, h); + + RecordingCanvas canvas = mNode.beginRecording(w, h); + Rect rect = new Rect(0, 0, w, h); + canvas.save(); + canvas.clipRect(0, 0, mLocation.right, mLocation.bottom); + canvas.drawBitmap(Bitmap.wrapHardwareBuffer(mImage.getHardwareBuffer(), COLOR_SPACE), + 0, 0, null); + canvas.restore(); + mNode.endRecording(); + return mNode; + } + + Rect getLocation() { + return mLocation; + } + + int getLeft() { + return mLocation.left; + } + + int getTop() { + return mLocation.top; + } + + int getRight() { + return mLocation.right; + } + + int getBottom() { + return mLocation.bottom; + } + + @Override + public void close() { + mImage.close(); + mNode.discardDisplayList(); + } + + @Override + public String toString() { + return "{location=" + mLocation + ", source=" + mImage + + ", buffer=" + mImage.getHardwareBuffer() + "}"; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java new file mode 100644 index 000000000000..8ff66f548172 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.screenshot; + +import android.graphics.Bitmap; +import android.graphics.HardwareRenderer; +import android.graphics.RecordingCanvas; +import android.graphics.Rect; +import android.graphics.RenderNode; +import android.graphics.drawable.Drawable; + +import androidx.annotation.UiThread; + +import java.util.ArrayList; +import java.util.List; + +/** + * Owns a series of partial screen captures (tiles). + * <p> + * To display on-screen, use {@link #getDrawable()}. + */ +@UiThread +class ImageTileSet { + + private static final String TAG = "ImageTileSet"; + + interface OnBoundsChangedListener { + /** + * Reports an update to the bounding box that contains all active tiles. These are virtual + * (capture) coordinates which can be either negative or positive. + */ + void onBoundsChanged(int left, int top, int right, int bottom); + } + + interface OnContentChangedListener { + /** + * Mark as dirty and rebuild display list. + */ + void onContentChanged(); + } + + private final List<ImageTile> mTiles = new ArrayList<>(); + private final Rect mBounds = new Rect(); + + private OnContentChangedListener mOnContentChangedListener; + private OnBoundsChangedListener mOnBoundsChangedListener; + + void setOnBoundsChangedListener(OnBoundsChangedListener listener) { + mOnBoundsChangedListener = listener; + } + + void setOnContentChangedListener(OnContentChangedListener listener) { + mOnContentChangedListener = listener; + } + + void addTile(ImageTile tile) { + final Rect newBounds = new Rect(mBounds); + final Rect newRect = tile.getLocation(); + mTiles.add(tile); + newBounds.union(newRect); + if (!newBounds.equals(mBounds)) { + mBounds.set(newBounds); + if (mOnBoundsChangedListener != null) { + mOnBoundsChangedListener.onBoundsChanged( + newBounds.left, newBounds.top, newBounds.right, newBounds.bottom); + } + } + if (mOnContentChangedListener != null) { + mOnContentChangedListener.onContentChanged(); + } + } + + /** + * Returns a drawable to paint the combined contents of the tiles. Drawable dimensions are + * zero-based and map directly to {@link #getLeft()}, {@link #getTop()}, {@link #getRight()}, + * and {@link #getBottom()} which are dimensions relative to the capture start position + * (positive or negative). + * + * @return a drawable to display the image content + */ + Drawable getDrawable() { + return new TiledImageDrawable(this); + } + + boolean isEmpty() { + return mTiles.isEmpty(); + } + + int size() { + return mTiles.size(); + } + + ImageTile get(int i) { + return mTiles.get(i); + } + + Bitmap toBitmap() { + if (mTiles.isEmpty()) { + return null; + } + final RenderNode output = new RenderNode("Bitmap Export"); + output.setPosition(0, 0, getWidth(), getHeight()); + RecordingCanvas canvas = output.beginRecording(); + canvas.translate(-getLeft(), -getTop()); + for (ImageTile tile : mTiles) { + canvas.save(); + canvas.translate(tile.getLeft(), tile.getTop()); + canvas.drawRenderNode(tile.getDisplayList()); + canvas.restore(); + } + output.endRecording(); + return HardwareRenderer.createHardwareBitmap(output, getWidth(), getHeight()); + } + + int getLeft() { + return mBounds.left; + } + + int getTop() { + return mBounds.top; + } + + int getRight() { + return mBounds.right; + } + + int getBottom() { + return mBounds.bottom; + } + + int getWidth() { + return mBounds.width(); + } + + int getHeight() { + return mBounds.height(); + } + + void clear() { + mBounds.set(0, 0, 0, 0); + mTiles.forEach(ImageTile::close); + mTiles.clear(); + if (mOnBoundsChangedListener != null) { + mOnBoundsChangedListener.onBoundsChanged(0, 0, 0, 0); + } + if (mOnContentChangedListener != null) { + mOnContentChangedListener.onContentChanged(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 334693589503..db2750b8842f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -27,8 +27,6 @@ import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipDescription; import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -36,43 +34,26 @@ import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.os.Handler; -import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; -import android.provider.MediaStore; -import android.provider.MediaStore.MediaColumns; import android.text.TextUtils; -import android.text.format.DateUtils; import android.util.Log; -import androidx.exifinterface.media.ExifInterface; - import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.R; import com.android.systemui.SystemUIFactory; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ShareTransition; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Objects; import java.util.Random; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -99,14 +80,17 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final boolean mSmartActionsEnabled; private final Random mRandom = new Random(); private final Supplier<ShareTransition> mSharedElementTransition; + private final ImageExporter mImageExporter; - SaveImageInBackgroundTask(Context context, ScreenshotSmartActions screenshotSmartActions, + SaveImageInBackgroundTask(Context context, ImageExporter exporter, + ScreenshotSmartActions screenshotSmartActions, ScreenshotController.SaveImageInBackgroundData data, Supplier<ShareTransition> sharedElementTransition) { mContext = context; mScreenshotSmartActions = screenshotSmartActions; mImageData = new ScreenshotController.SavedImageData(); mSharedElementTransition = sharedElementTransition; + mImageExporter = exporter; // Prepare all the output metadata mParams = data; @@ -139,90 +123,17 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { } Thread.currentThread().setPriority(Thread.MAX_PRIORITY); - ContentResolver resolver = mContext.getContentResolver(); Bitmap image = mParams.image; try { - // Save the screenshot to the MediaStore - final ContentValues values = new ContentValues(); - values.put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES - + File.separator + Environment.DIRECTORY_SCREENSHOTS); - values.put(MediaColumns.DISPLAY_NAME, mImageFileName); - values.put(MediaColumns.MIME_TYPE, "image/png"); - values.put(MediaColumns.DATE_ADDED, mImageTime / 1000); - values.put(MediaColumns.DATE_MODIFIED, mImageTime / 1000); - values.put(MediaColumns.DATE_EXPIRES, (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000); - values.put(MediaColumns.IS_PENDING, 1); - - final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + // Call synchronously here since already on a background thread. + Uri uri = mImageExporter.export(Runnable::run, image).get(); CompletableFuture<List<Notification.Action>> smartActionsFuture = mScreenshotSmartActions.getSmartActionsFuture( mScreenshotId, uri, image, mSmartActionsProvider, mSmartActionsEnabled, getUserHandle(mContext)); - try { - // First, write the actual data for our screenshot - try (OutputStream out = resolver.openOutputStream(uri)) { - if (DEBUG_STORAGE) { - Log.d(TAG, "Compressing PNG:" - + " w=" + image.getWidth() + " h=" + image.getHeight()); - } - if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) { - if (DEBUG_STORAGE) { - Log.d(TAG, "Bitmap.compress returned false"); - } - throw new IOException("Failed to compress"); - } - if (DEBUG_STORAGE) { - Log.d(TAG, "Done compressing PNG"); - } - } - - // Next, write metadata to help index the screenshot - try (ParcelFileDescriptor pfd = resolver.openFile(uri, "rw", null)) { - final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor()); - - exif.setAttribute(ExifInterface.TAG_SOFTWARE, - "Android " + Build.DISPLAY); - - exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, - Integer.toString(image.getWidth())); - exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, - Integer.toString(image.getHeight())); - - final ZonedDateTime time = ZonedDateTime.ofInstant( - Instant.ofEpochMilli(mImageTime), ZoneId.systemDefault()); - exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, - DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(time)); - exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, - DateTimeFormatter.ofPattern("SSS").format(time)); - - if (Objects.equals(time.getOffset(), ZoneOffset.UTC)) { - exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00"); - } else { - exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, - DateTimeFormatter.ofPattern("XXX").format(time)); - } - if (DEBUG_STORAGE) { - Log.d(TAG, "Writing EXIF metadata"); - } - exif.saveAttributes(); - } - - // Everything went well above, publish it! - values.clear(); - values.put(MediaColumns.IS_PENDING, 0); - values.putNull(MediaColumns.DATE_EXPIRES); - resolver.update(uri, values, null, null); - if (DEBUG_STORAGE) { - Log.d(TAG, "Completed writing to ContentManager"); - } - } catch (Exception e) { - resolver.delete(uri, null); - throw e; - } - List<Notification.Action> smartActions = new ArrayList<>(); if (mSmartActionsEnabled) { int timeoutMs = DeviceConfig.getInt( diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index d2fe5d284a97..a60c24146d9f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -78,10 +78,13 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ShareTransition; import com.android.systemui.util.DeviceConfigProxy; import java.util.List; +import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.Supplier; @@ -166,6 +169,9 @@ public class ScreenshotController { private final ScreenshotNotificationsController mNotificationsController; private final ScreenshotSmartActions mScreenshotSmartActions; private final UiEventLogger mUiEventLogger; + private final ImageExporter mImageExporter; + private final Executor mMainExecutor; + private final Executor mBgExecutor; private final WindowManager mWindowManager; private final WindowManager.LayoutParams mWindowLayoutParams; @@ -219,11 +225,17 @@ public class ScreenshotController { ScreenshotNotificationsController screenshotNotificationsController, ScrollCaptureClient scrollCaptureClient, UiEventLogger uiEventLogger, - DeviceConfigProxy configProxy) { + DeviceConfigProxy configProxy, + ImageExporter imageExporter, + @Main Executor mainExecutor, + @Background Executor bgExecutor) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; mScrollCaptureClient = scrollCaptureClient; mUiEventLogger = uiEventLogger; + mImageExporter = imageExporter; + mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; final DisplayManager dm = requireNonNull(context.getSystemService(DisplayManager.class)); final Display display = dm.getDisplay(DEFAULT_DISPLAY); @@ -502,9 +514,10 @@ public class ScreenshotController { } } - private void runScrollCapture(ScrollCaptureClient.Connection connection, - Runnable after) { - new ScrollCaptureController(mContext, connection).run(after); + private void runScrollCapture(ScrollCaptureClient.Connection connection, Runnable andThen) { + ScrollCaptureController controller = new ScrollCaptureController(mContext, connection, + mMainExecutor, mBgExecutor, mImageExporter); + controller.run(andThen); } /** @@ -604,8 +617,8 @@ public class ScreenshotController { mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady); } - mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data, - getShareTransitionSupplier()); + mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mImageExporter, + mScreenshotSmartActions, data, getShareTransitionSupplier()); mSaveInBgTask.execute(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java index e159992bc9a5..bb07012f2355 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java @@ -18,6 +18,7 @@ package com.android.systemui.screenshot; import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL; +import static java.lang.Math.min; import static java.util.Objects.requireNonNull; import android.annotation.UiContext; @@ -44,15 +45,20 @@ import java.util.function.Consumer; import javax.inject.Inject; /** - * High level interface to scroll capture API. + * High(er) level interface to scroll capture API. */ public class ScrollCaptureClient { + private static final int TILE_SIZE_PX_MAX = 4 * (1024 * 1024); + private static final int TILES_PER_PAGE = 2; // increase once b/174571735 is addressed + private static final int MAX_PAGES = 5; + private static final int MAX_IMAGE_COUNT = MAX_PAGES * TILES_PER_PAGE; @VisibleForTesting static final int MATCH_ANY_TASK = ActivityTaskManager.INVALID_TASK_ID; private static final String TAG = LogConfig.logTag(ScrollCaptureClient.class); + /** * A connection to a remote window. Starts a capture session. */ @@ -60,13 +66,10 @@ public class ScrollCaptureClient { /** * Session start should be deferred until UI is active because of resource allocation and * potential visible side effects in the target window. - * - * @param maxBuffers the maximum number of buffers (tiles) that may be in use at one - * time, tiles are not cached anywhere so set this to a large enough - * number to retain offscreen content until it is no longer needed + * @param sessionConsumer listener to receive the session once active */ - void start(int maxBuffers, Consumer<Session> sessionConsumer); + void start(Consumer<Session> sessionConsumer); /** * Close the connection. @@ -100,26 +103,33 @@ public class ScrollCaptureClient { */ interface Session { /** - * Request the given horizontal strip. Values are y-coordinates in captured space, relative - * to start position. + * Request an image tile at the given position, from top, to top + {@link #getTileHeight()}, + * and from left 0, to {@link #getPageWidth()} * - * @param contentRect the area to capture, in content rect space, relative to scroll-bounds + * @param top the top (y) position of the tile to capture, in content rect space * @param consumer listener to be informed of the result */ - void requestTile(Rect contentRect, Consumer<CaptureResult> consumer); + void requestTile(int top, Consumer<CaptureResult> consumer); /** - * End the capture session, return the target app to original state. The returned - * stage must be waited for to complete to allow the target app a chance to restore to - * original state before becoming visible. + * Returns the maximum number of tiles which may be requested and retained without + * being {@link Image#close() closed}. * - * @return a stage presenting the session shutdown + * @return the maximum number of open tiles allowed */ - void end(Runnable listener); + int getMaxTiles(); + + int getTileHeight(); + + int getPageHeight(); - int getMaxTileHeight(); + int getPageWidth(); - int getMaxTileWidth(); + /** + * End the capture session, return the target app to original state. The listener + * will be called when the target app is ready to before visible and interactive. + */ + void end(Runnable listener); } private final IWindowManager mWindowManagerService; @@ -131,6 +141,12 @@ public class ScrollCaptureClient { mWindowManagerService = windowManagerService; } + /** + * Set the window token for the screenshot window/ This is required to avoid targeting our + * window or any above it. + * + * @param token the windowToken of the screenshot window + */ public void setHostWindowToken(IBinder token) { mHostWindowToken = token; } @@ -176,6 +192,8 @@ public class ScrollCaptureClient { private ImageReader mReader; private Rect mScrollBounds; + private int mTileHeight; + private int mTileWidth; private Rect mRequestRect; private boolean mStarted; @@ -197,6 +215,15 @@ public class ScrollCaptureClient { mScrollBounds = scrollBounds; mConnectionConsumer.accept(this); mConnectionConsumer = null; + + int pxPerPage = mScrollBounds.width() * mScrollBounds.height(); + int pxPerTile = min(TILE_SIZE_PX_MAX, (pxPerPage / TILES_PER_PAGE)); + mTileWidth = mScrollBounds.width(); + mTileHeight = pxPerTile / mScrollBounds.width(); + if (DEBUG_SCROLL) { + Log.d(TAG, "scrollBounds: " + mScrollBounds); + Log.d(TAG, "tile dimen: " + mTileWidth + "x" + mTileHeight); + } } @Override @@ -257,24 +284,19 @@ public class ScrollCaptureClient { // ScrollCaptureController.Connection - // -> Error handling: BiConsumer<Session, Throwable> ? @Override - public void start(int maxBufferCount, Consumer<Session> sessionConsumer) { + public void start(Consumer<Session> sessionConsumer) { if (DEBUG_SCROLL) { - Log.d(TAG, "start(maxBufferCount=" + maxBufferCount - + ", sessionConsumer=" + sessionConsumer + ")"); + Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ")"); } - mReader = ImageReader.newInstance(mScrollBounds.width(), mScrollBounds.height(), - PixelFormat.RGBA_8888, maxBufferCount, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); + mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888, + MAX_IMAGE_COUNT, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); mSessionConsumer = sessionConsumer; try { mConnection.startCapture(mReader.getSurface()); mStarted = true; } catch (RemoteException e) { - Log.w(TAG, "should not be happening :-("); - // ? - //mSessionListener.onError(e); - //mSessionListener = null; + Log.w(TAG, "Failed to start", e); } } @@ -307,27 +329,36 @@ public class ScrollCaptureClient { } @Override - public int getMaxTileHeight() { + public int getPageHeight() { return mScrollBounds.height(); } @Override - public int getMaxTileWidth() { + public int getPageWidth() { return mScrollBounds.width(); } @Override - public void requestTile(Rect contentRect, Consumer<CaptureResult> consumer) { + public int getTileHeight() { + return mTileHeight; + } + + @Override + public int getMaxTiles() { + return MAX_IMAGE_COUNT; + } + + @Override + public void requestTile(int top, Consumer<CaptureResult> consumer) { if (DEBUG_SCROLL) { - Log.d(TAG, "requestTile(contentRect=" + contentRect + "consumer=" + consumer + ")"); + Log.d(TAG, "requestTile(top=" + top + ", consumer=" + consumer + ")"); } - mRequestRect = new Rect(contentRect); + mRequestRect = new Rect(0, top, mTileWidth, top + mTileHeight); mResultConsumer = consumer; try { mConnection.requestImage(mRequestRect); } catch (RemoteException e) { Log.e(TAG, "Caught remote exception from requestImage", e); - // ? } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index 47b4a508f401..c75efbcc5f80 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -16,44 +16,21 @@ package com.android.systemui.screenshot; -import static android.graphics.ColorSpace.Named.SRGB; - -import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.ColorSpace; -import android.graphics.Picture; -import android.graphics.Rect; -import android.media.ExifInterface; -import android.media.Image; import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.os.ParcelFileDescriptor; import android.os.UserHandle; -import android.provider.MediaStore; -import android.text.format.DateUtils; import android.util.Log; import android.widget.Toast; import com.android.systemui.screenshot.ScrollCaptureClient.Connection; import com.android.systemui.screenshot.ScrollCaptureClient.Session; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.sql.Date; -import java.text.SimpleDateFormat; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Objects; -import java.util.UUID; +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -62,16 +39,27 @@ import java.util.function.Consumer; public class ScrollCaptureController { private static final String TAG = "ScrollCaptureController"; + private static final boolean USE_TILED_IMAGE = false; + public static final int MAX_PAGES = 5; public static final int MAX_HEIGHT = 12000; private final Connection mConnection; private final Context mContext; - private Picture mPicture; - public ScrollCaptureController(Context context, Connection connection) { + private final Executor mUiExecutor; + private final Executor mBgExecutor; + private final ImageExporter mImageExporter; + private final ImageTileSet mImageTileSet; + + public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor, + Executor bgExecutor, ImageExporter exporter) { mContext = context; mConnection = connection; + mUiExecutor = uiExecutor; + mBgExecutor = bgExecutor; + mImageExporter = exporter; + mImageTileSet = new ImageTileSet(); } /** @@ -80,159 +68,58 @@ public class ScrollCaptureController { * @param after action to take after the flow is complete */ public void run(final Runnable after) { - mConnection.start(MAX_PAGES, (session) -> startCapture(session, after)); + mConnection.start((session) -> startCapture(session, after)); } - private void startCapture(Session session, final Runnable after) { - Rect requestRect = new Rect(0, 0, - session.getMaxTileWidth(), session.getMaxTileHeight()); + private void startCapture(Session session, final Runnable onDismiss) { Consumer<ScrollCaptureClient.CaptureResult> consumer = new Consumer<ScrollCaptureClient.CaptureResult>() { int mFrameCount = 0; + int mTop = 0; @Override public void accept(ScrollCaptureClient.CaptureResult result) { mFrameCount++; + boolean emptyFrame = result.captured.height() == 0; if (!emptyFrame) { - mPicture = stackBelow(mPicture, result.image, result.captured.width(), - result.captured.height()); + mImageTileSet.addTile(new ImageTile(result.image, result.captured)); } + if (emptyFrame || mFrameCount >= MAX_PAGES - || requestRect.bottom > MAX_HEIGHT) { - Uri uri = null; - if (mPicture != null) { - // This is probably on a binder thread right now ¯\_(ツ)_/¯ - uri = writeImage(Bitmap.createBitmap(mPicture)); - // Release those buffers! - mPicture.close(); - } - if (uri != null) { - launchViewer(uri); + || mTop + session.getTileHeight() > MAX_HEIGHT) { + if (!mImageTileSet.isEmpty()) { + exportToFile(mImageTileSet.toBitmap(), session, onDismiss); + mImageTileSet.clear(); } else { - Toast.makeText(mContext, "Failed to create tall screenshot", - Toast.LENGTH_SHORT).show(); + session.end(onDismiss); } - session.end(after); // end session, close connection, after.run() return; } - requestRect.offset(0, session.getMaxTileHeight()); - session.requestTile(requestRect, /* consumer */ this); + mTop += result.captured.height(); + session.requestTile(mTop, /* consumer */ this); } }; // fire it up! - session.requestTile(requestRect, consumer); + session.requestTile(0, consumer); }; - - /** - * Combine the top {@link Picture} with an {@link Image} by appending the image directly - * below, creating a result that is the combined height of both. - * <p> - * Note: no pixel data is transferred here, only a record of drawing commands. Backing - * hardware buffers must not be modified/recycled until the picture is - * {@link Picture#close closed}. - * - * @param top the existing picture - * @param below the image to append below - * @param cropWidth the width of the pixel data to use from the image - * @param cropHeight the height of the pixel data to use from the image - * - * @return a new Picture which draws the previous picture with the image below it - */ - private static Picture stackBelow(Picture top, Image below, int cropWidth, int cropHeight) { - int width = cropWidth; - int height = cropHeight; - if (top != null) { - height += top.getHeight(); - width = Math.max(width, top.getWidth()); - } - Picture combined = new Picture(); - Canvas canvas = combined.beginRecording(width, height); - int y = 0; - if (top != null) { - canvas.drawPicture(top, new Rect(0, 0, top.getWidth(), top.getHeight())); - y += top.getHeight(); - } - canvas.drawBitmap(Bitmap.wrapHardwareBuffer( - below.getHardwareBuffer(), ColorSpace.get(SRGB)), 0, y, null); - combined.endRecording(); - return combined; - } - - Uri writeImage(Bitmap image) { - ContentResolver resolver = mContext.getContentResolver(); - long mImageTime = System.currentTimeMillis(); - String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime)); - String mImageFileName = String.format("tall_Screenshot_%s.png", imageDate); - String mScreenshotId = String.format("Screenshot_%s", UUID.randomUUID()); - try { - // Save the screenshot to the MediaStore - final ContentValues values = new ContentValues(); - values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES - + File.separator + Environment.DIRECTORY_SCREENSHOTS); - values.put(MediaStore.MediaColumns.DISPLAY_NAME, mImageFileName); - values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png"); - values.put(MediaStore.MediaColumns.DATE_ADDED, mImageTime / 1000); - values.put(MediaStore.MediaColumns.DATE_MODIFIED, mImageTime / 1000); - values.put( - MediaStore.MediaColumns.DATE_EXPIRES, - (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000); - values.put(MediaStore.MediaColumns.IS_PENDING, 1); - - final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - values); + void exportToFile(Bitmap bitmap, Session session, Runnable afterEnd) { + mImageExporter.setFormat(Bitmap.CompressFormat.PNG); + mImageExporter.setQuality(6); + ListenableFuture<Uri> future = + mImageExporter.export(mBgExecutor, bitmap); + future.addListener(() -> { try { - try (OutputStream out = resolver.openOutputStream(uri)) { - if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) { - throw new IOException("Failed to compress"); - } - } - - // Next, write metadata to help index the screenshot - try (ParcelFileDescriptor pfd = resolver.openFile(uri, "rw", null)) { - final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor()); - - exif.setAttribute(ExifInterface.TAG_SOFTWARE, - "Android " + Build.DISPLAY); - - exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, - Integer.toString(image.getWidth())); - exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, - Integer.toString(image.getHeight())); - - final ZonedDateTime time = ZonedDateTime.ofInstant( - Instant.ofEpochMilli(mImageTime), ZoneId.systemDefault()); - exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, - DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(time)); - exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, - DateTimeFormatter.ofPattern("SSS").format(time)); - - if (Objects.equals(time.getOffset(), ZoneOffset.UTC)) { - exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00"); - } else { - exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, - DateTimeFormatter.ofPattern("XXX").format(time)); - } - exif.saveAttributes(); - } - - // Everything went well above, publish it! - values.clear(); - values.put(MediaStore.MediaColumns.IS_PENDING, 0); - values.putNull(MediaStore.MediaColumns.DATE_EXPIRES); - resolver.update(uri, values, null, null); - return uri; - } catch (Exception e) { - resolver.delete(uri, null); - throw e; + launchViewer(future.get()); + } catch (InterruptedException | ExecutionException e) { + Toast.makeText(mContext, "Failed to write image", Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Error storing screenshot to media store", e.getCause()); } - } catch (Exception e) { - Log.e(TAG, "unable to save screenshot", e); - } - return null; + session.end(afterEnd); // end session, close connection, afterEnd.run() + }, mUiExecutor); } void launchViewer(Uri uri) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java new file mode 100644 index 000000000000..72f489bdd398 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.screenshot; + +import android.annotation.Nullable; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RenderNode; +import android.graphics.drawable.Drawable; +import android.util.Log; + +/** + * Draws a set of hardware image tiles from a display list. The tiles exist in virtual coordinate + * space that may extend into positive or negative values. The origin is the upper-left-most corner + * of bounding box, which is drawn at 0,0 to the drawable (output) bounds. + */ +public class TiledImageDrawable extends Drawable { + + private static final String TAG = "TiledImageDrawable"; + + private final ImageTileSet mTiles; + private RenderNode mNode; + + public TiledImageDrawable(ImageTileSet tiles) { + mTiles = tiles; + mTiles.setOnContentChangedListener(this::onContentChanged); + } + + private void onContentChanged() { + if (mNode != null && mNode.hasDisplayList()) { + mNode.discardDisplayList(); + } + invalidateSelf(); + } + + private void rebuildDisplayListIfNeeded() { + if (mNode != null && mNode.hasDisplayList()) { + return; + } + if (mNode == null) { + mNode = new RenderNode("TiledImageDrawable"); + } + mNode.setPosition(0, 0, mTiles.getWidth(), mTiles.getHeight()); + Canvas canvas = mNode.beginRecording(mTiles.getWidth(), mTiles.getHeight()); + // Align content (virtual) top/left with 0,0, within the render node + canvas.translate(-mTiles.getLeft(), -mTiles.getTop()); + for (int i = 0; i < mTiles.size(); i++) { + ImageTile tile = mTiles.get(i); + canvas.save(); + canvas.translate(tile.getLeft(), tile.getTop()); + canvas.drawRenderNode(tile.getDisplayList()); + canvas.restore(); + } + mNode.endRecording(); + } + + /** + * Draws the tiled image to the canvas, with the top/left (virtual) coordinate aligned to 0,0 + * placed at left/top of the drawable's bounds. + */ + @Override + public void draw(Canvas canvas) { + rebuildDisplayListIfNeeded(); + if (canvas.isHardwareAccelerated()) { + Rect bounds = getBounds(); + canvas.save(); + canvas.clipRect(bounds); + canvas.translate(bounds.left, bounds.top); + canvas.drawRenderNode(mNode); + canvas.restore(); + } else { + Log.d(TAG, "Canvas is not hardware accelerated. Skipping draw!"); + } + } + + @Override + public int getIntrinsicWidth() { + return mTiles.getWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mTiles.getHeight(); + } + + @Override + public void setAlpha(int alpha) { + if (mNode.setAlpha(alpha / 255f)) { + invalidateSelf(); + } + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + throw new IllegalArgumentException("not implemented"); + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index 3811ca929f6e..bbc4b780bc52 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 @@ public class FeatureFlags { @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 @@ public class FeatureFlags { 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/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java index dbee0ee8f5d5..9ef304d7e83c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java @@ -20,6 +20,8 @@ import android.app.Notification; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.TypedValue; import android.view.NotificationHeaderView; import android.view.View; import android.view.ViewGroup; @@ -456,12 +458,14 @@ public class NotificationGroupingUtil { if (target == null) { return; } - Integer value = (Integer) target.getTag(iconVisible + final Integer data = (Integer) target.getTag(iconVisible ? com.android.internal.R.id.tag_margin_end_when_icon_visible : com.android.internal.R.id.tag_margin_end_when_icon_gone); - if (value == null) { + if (data == null) { return; } + final DisplayMetrics metrics = target.getResources().getDisplayMetrics(); + final int value = TypedValue.complexToDimensionPixelOffset(data, metrics); if (target instanceof NotificationHeaderView) { ((NotificationHeaderView) target).setTopLineExtraMarginEnd(value); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 28ee9358737e..97201f5c9a34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -134,12 +134,14 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { /** Shows or hides feedback indicator */ @Override public void showFeedbackIcon(boolean show, Pair<Integer, Integer> resIds) { - mFeedbackIcon.setVisibility(show ? View.VISIBLE : View.GONE); - if (show) { - if (mFeedbackIcon instanceof ImageButton) { - ((ImageButton) mFeedbackIcon).setImageResource(resIds.first); + if (mFeedbackIcon != null) { + mFeedbackIcon.setVisibility(show ? View.VISIBLE : View.GONE); + if (show) { + if (mFeedbackIcon instanceof ImageButton) { + ((ImageButton) mFeedbackIcon).setImageResource(resIds.first); + } + mFeedbackIcon.setContentDescription(mView.getContext().getString(resIds.second)); } - mFeedbackIcon.setContentDescription(mView.getContext().getString(resIds.second)); } } @@ -263,7 +265,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon); mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_EXPANDER, mExpandButton); - mTransformationHelper.addViewTransformingToSimilar(mWorkProfileImage); + if (mWorkProfileImage != null) { + mTransformationHelper.addViewTransformingToSimilar(mWorkProfileImage); + } if (mIsLowPriority && mHeaderText != null) { mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mHeaderText); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index 6da5d1b90cd9..6cd7a74cdf02 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -112,6 +112,11 @@ public class KeyguardClockPositionAlgorithm { private int mBurnInPreventionOffsetY; /** + * Burn-in prevention y translation for large clock layouts. + */ + private int mBurnInPreventionOffsetYLargeClock; + + /** * Doze/AOD transition amount. */ private float mDarkAmount; @@ -156,6 +161,8 @@ public class KeyguardClockPositionAlgorithm { R.dimen.burn_in_prevention_offset_x); mBurnInPreventionOffsetY = res.getDimensionPixelSize( R.dimen.burn_in_prevention_offset_y); + mBurnInPreventionOffsetYLargeClock = res.getDimensionPixelSize( + R.dimen.burn_in_prevention_offset_y_large_clock); } /** @@ -287,8 +294,12 @@ public class KeyguardClockPositionAlgorithm { } private float burnInPreventionOffsetY() { - return getBurnInOffset(mBurnInPreventionOffsetY * 2, false /* xAxis */) - - mBurnInPreventionOffsetY; + int offset = mBurnInPreventionOffsetY; + if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) { + offset = mBurnInPreventionOffsetYLargeClock; + } + + return getBurnInOffset(offset * 2, false /* xAxis */) - offset; } private float burnInPreventionOffsetX() { 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 53d02280a03b..ab58286859cc 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 @@ public class AccessPointControllerImpl 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 @@ public class AccessPointControllerImpl 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 @@ public class AccessPointControllerImpl 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 @@ public class AccessPointControllerImpl } } - 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); + 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(int state) { + public void onWifiStateChanged() { + scanForAccessPoints(); } @Override - public void onConnectedChanged() { - fireAcccessPointsCallback(mWifiTracker.getAccessPoints()); + public void onWifiEntriesChanged() { + scanForAccessPoints(); } @Override - public void onAccessPointsChanged() { - fireAcccessPointsCallback(mWifiTracker.getAccessPoints()); + public void onNumSavedNetworksChanged() { + // Do nothing } - private final ActionListener mConnectListener = new ActionListener() { - @Override - public void onSuccess() { - if (DEBUG) Log.d(TAG, "connect success"); - } + @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 f92860b70116..b012dc4c7159 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.content.Intent; 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 @@ public interface NetworkController extends CallbackController<SignalCallback>, D 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 291aefef7d9d..5f5a83c5c50c 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 @@ public class NetworkControllerImpl extends BroadcastReceiver TelephonyManager telephonyManager, @Nullable WifiManager wifiManager, NetworkScoreManager networkScoreManager, + AccessPointControllerImpl accessPointController, DemoModeController demoModeController) { this(context, connectivityManager, telephonyManager, @@ -194,7 +195,7 @@ public class NetworkControllerImpl extends BroadcastReceiver networkScoreManager, SubscriptionManager.from(context), Config.readConfig(context), bgLooper, new CallbackHandler(), - new AccessPointControllerImpl(context), + accessPointController, new DataUsageController(context), new SubscriptionDefaults(), deviceProvisionedController, @@ -359,7 +360,7 @@ public class NetworkControllerImpl extends BroadcastReceiver // broadcasts IntentFilter filter = new IntentFilter(); - filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); filter.addAction(Intent.ACTION_SIM_STATE_CHANGED); filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java index 9db109de369b..66e8082f2226 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy; +import com.android.settingslib.AccessibilityContentDescriptions; +import com.android.settingslib.SignalIcon.IconGroup; import com.android.systemui.R; public class WifiIcons { @@ -40,6 +42,7 @@ public class WifiIcons { WIFI_NO_INTERNET_ICONS, WIFI_FULL_ICONS }; + static final int[][] WIFI_SIGNAL_STRENGTH = QS_WIFI_SIGNAL_STRENGTH; public static final int QS_WIFI_DISABLED = com.android.internal.R.drawable.ic_wifi_signal_0; @@ -47,4 +50,16 @@ public class WifiIcons { static final int WIFI_NO_NETWORK = QS_WIFI_NO_NETWORK; static final int WIFI_LEVEL_COUNT = WIFI_SIGNAL_STRENGTH[0].length; + + public static final IconGroup UNMERGED_WIFI = new IconGroup( + "Wi-Fi Icons", + WifiIcons.WIFI_SIGNAL_STRENGTH, + WifiIcons.QS_WIFI_SIGNAL_STRENGTH, + AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH, + WifiIcons.WIFI_NO_NETWORK, + WifiIcons.QS_WIFI_NO_NETWORK, + WifiIcons.WIFI_NO_NETWORK, + WifiIcons.QS_WIFI_NO_NETWORK, + AccessibilityContentDescriptions.WIFI_NO_CONNECTION + ); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index 6d109ac8d4a2..4954286af3fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -25,12 +25,15 @@ import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; +import android.text.Html; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; -import com.android.settingslib.AccessibilityContentDescriptions; import com.android.settingslib.SignalIcon.IconGroup; +import com.android.settingslib.SignalIcon.MobileIconGroup; import com.android.settingslib.SignalIcon.State; +import com.android.settingslib.graph.SignalDrawable; +import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.wifi.WifiStatusTracker; import com.android.systemui.R; import com.android.systemui.statusbar.policy.NetworkController.IconState; @@ -42,6 +45,9 @@ public class WifiSignalController extends SignalController<WifiSignalController.WifiState, IconGroup> { private final boolean mHasMobileDataFeature; private final WifiStatusTracker mWifiTracker; + private final IconGroup mUnmergedWifiIconGroup = WifiIcons.UNMERGED_WIFI; + private final MobileIconGroup mCarrierMergedWifiIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI; + private final WifiManager mWifiManager; public WifiSignalController(Context context, boolean hasMobileDataFeature, CallbackHandler callbackHandler, NetworkControllerImpl networkController, @@ -49,6 +55,7 @@ public class WifiSignalController extends NetworkScoreManager networkScoreManager) { super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI, callbackHandler, networkController); + mWifiManager = wifiManager; mWifiTracker = new WifiStatusTracker(mContext, wifiManager, networkScoreManager, connectivityManager, this::handleStatusUpdated); mWifiTracker.setListening(true); @@ -57,18 +64,7 @@ public class WifiSignalController extends wifiManager.registerTrafficStateCallback(context.getMainExecutor(), new WifiTrafficStateCallback()); } - // WiFi only has one state. - mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup( - "Wi-Fi Icons", - WifiIcons.WIFI_SIGNAL_STRENGTH, - WifiIcons.QS_WIFI_SIGNAL_STRENGTH, - AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH, - WifiIcons.WIFI_NO_NETWORK, - WifiIcons.QS_WIFI_NO_NETWORK, - WifiIcons.WIFI_NO_NETWORK, - WifiIcons.QS_WIFI_NO_NETWORK, - AccessibilityContentDescriptions.WIFI_NO_CONNECTION - ); + mCurrentState.iconGroup = mLastState.iconGroup = mUnmergedWifiIconGroup; } @Override @@ -82,6 +78,14 @@ public class WifiSignalController extends @Override public void notifyListeners(SignalCallback callback) { + if (mCurrentState.isCarrierMerged) { + notifyListenersForCarrierWifi(callback); + } else { + notifyListenersForNonCarrierWifi(callback); + } + } + + private void notifyListenersForNonCarrierWifi(SignalCallback callback) { // only show wifi in the cluster if connected or if wifi-only boolean visibleWhenEnabled = mContext.getResources().getBoolean( R.bool.config_showWifiIndicatorWhenEnabled); @@ -104,14 +108,49 @@ public class WifiSignalController extends wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel); } - private void copyWifiStates() { - mCurrentState.enabled = mWifiTracker.enabled; - mCurrentState.isDefault = mWifiTracker.isDefaultNetwork; - mCurrentState.connected = mWifiTracker.connected; - mCurrentState.ssid = mWifiTracker.ssid; - mCurrentState.rssi = mWifiTracker.rssi; - mCurrentState.level = mWifiTracker.level; - mCurrentState.statusLabel = mWifiTracker.statusLabel; + private void notifyListenersForCarrierWifi(SignalCallback callback) { + MobileIconGroup icons = mCarrierMergedWifiIconGroup; + String contentDescription = getTextIfExists(getContentDescription()).toString(); + CharSequence dataContentDescriptionHtml = getTextIfExists(icons.dataContentDescription); + + CharSequence dataContentDescription = Html.fromHtml( + dataContentDescriptionHtml.toString(), 0).toString(); + if (mCurrentState.inetCondition == 0) { + dataContentDescription = mContext.getString(R.string.data_connection_no_internet); + } + boolean qsVisible = mCurrentState.enabled + && (mCurrentState.connected && mCurrentState.inetCondition == 1); + + IconState statusIcon = + new IconState(qsVisible, getCurrentIconIdForCarrierWifi(), contentDescription); + int qsTypeIcon = mCurrentState.connected ? icons.qsDataType : 0; + int typeIcon = mCurrentState.connected ? icons.dataType : 0; + IconState qsIcon = new IconState( + mCurrentState.connected, getQsCurrentIconIdForCarrierWifi(), contentDescription); + CharSequence description = mNetworkController.getMobileDataNetworkName(); + callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon, + mCurrentState.activityIn, mCurrentState.activityOut, dataContentDescription, + dataContentDescriptionHtml, description, icons.isWide, + mCurrentState.subId, /* roaming= */ false); + } + + private int getCurrentIconIdForCarrierWifi() { + int level = mCurrentState.level; + // The WiFi signal level returned by WifiManager#calculateSignalLevel start from 0, so + // WifiManager#getMaxSignalLevel + 1 represents the total level buckets count. + int totalLevel = mWifiManager.getMaxSignalLevel() + 1; + boolean noInternet = mCurrentState.inetCondition == 0; + if (mCurrentState.connected) { + return SignalDrawable.getState(level, totalLevel, noInternet); + } else if (mCurrentState.enabled) { + return SignalDrawable.getEmptyState(totalLevel); + } else { + return 0; + } + } + + private int getQsCurrentIconIdForCarrierWifi() { + return getCurrentIconIdForCarrierWifi(); } /** @@ -137,6 +176,21 @@ public class WifiSignalController extends notifyListenersIfNecessary(); } + private void copyWifiStates() { + mCurrentState.enabled = mWifiTracker.enabled; + mCurrentState.isDefault = mWifiTracker.isDefaultNetwork; + mCurrentState.connected = mWifiTracker.connected; + mCurrentState.ssid = mWifiTracker.ssid; + mCurrentState.rssi = mWifiTracker.rssi; + mCurrentState.level = mWifiTracker.level; + mCurrentState.statusLabel = mWifiTracker.statusLabel; + mCurrentState.isCarrierMerged = mWifiTracker.isCarrierMerged; + mCurrentState.subId = mWifiTracker.subId; + mCurrentState.iconGroup = + mCurrentState.isCarrierMerged ? mCarrierMergedWifiIconGroup + : mUnmergedWifiIconGroup; + } + @VisibleForTesting void setActivity(int wifiActivity) { mCurrentState.activityIn = wifiActivity == DATA_ACTIVITY_INOUT @@ -157,10 +211,12 @@ public class WifiSignalController extends } static class WifiState extends State { - String ssid; - boolean isTransient; - boolean isDefault; - String statusLabel; + public String ssid; + public boolean isTransient; + public boolean isDefault; + public String statusLabel; + public boolean isCarrierMerged; + public int subId; @Override public void copyFrom(State s) { @@ -170,6 +226,8 @@ public class WifiSignalController extends isTransient = state.isTransient; isDefault = state.isDefault; statusLabel = state.statusLabel; + isCarrierMerged = state.isCarrierMerged; + subId = state.subId; } @Override @@ -178,7 +236,9 @@ public class WifiSignalController extends builder.append(",ssid=").append(ssid) .append(",isTransient=").append(isTransient) .append(",isDefault=").append(isDefault) - .append(",statusLabel=").append(statusLabel); + .append(",statusLabel=").append(statusLabel) + .append(",isCarrierMerged=").append(isCarrierMerged) + .append(",subId=").append(subId); } @Override @@ -190,7 +250,9 @@ public class WifiSignalController extends return Objects.equals(other.ssid, ssid) && other.isTransient == isTransient && other.isDefault == isDefault - && TextUtils.equals(other.statusLabel, statusLabel); + && TextUtils.equals(other.statusLabel, statusLabel) + && other.isCarrierMerged == isCarrierMerged + && other.subId == subId; } } } 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 914105fdc4c4..069b4051af50 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.UserInfoControllerImpl; 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 @@ public interface StatusBarPolicyModule { @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 0d63324966fd..0ba072e9e72f 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 android.provider.Settings; 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 class NotificationChannels extends SystemUI { 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 125b5d4c7b8d..8a79acef756c 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java @@ -24,6 +24,7 @@ import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; @@ -31,12 +32,9 @@ import com.android.wm.shell.pip.PipMediaController; 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.PipControlsView; -import com.android.wm.shell.pip.tv.PipControlsViewController; -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.legacysplitscreen.LegacySplitScreen; +import com.android.wm.shell.pip.tv.TvPipNotificationController; import java.util.Optional; @@ -57,44 +55,24 @@ public abstract class TvPipModule { 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 PipControlsViewController providePipControlsViewController( - PipControlsView pipControlsView, PipController pipController) { - return new PipControlsViewController(pipControlsView, pipController); - } - - @WMSingleton - @Provides - static PipControlsView providePipControlsView(Context context) { - return new PipControlsView(context, null); - } - - @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); @@ -108,22 +86,32 @@ public abstract class TvPipModule { @WMSingleton @Provides - static TvPipMenuController providesPipTvMenuController(Context context, - PipBoundsState pipBoundsState, SystemWindows systemWindows) { - return new TvPipMenuController(context, pipBoundsState, systemWindows); + static TvPipMenuController providesTvPipMenuController( + Context context, + PipBoundsState pipBoundsState, + SystemWindows systemWindows, + PipMediaController pipMediaController) { + return new TvPipMenuController(context, pipBoundsState, systemWindows, pipMediaController); + } + + @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 765fd32f7cd2..e20cd44974bb 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java @@ -17,22 +17,21 @@ 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.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; - -import java.util.concurrent.Executor; +import com.android.wm.shell.transition.Transitions; import dagger.Module; import dagger.Provides; @@ -46,9 +45,9 @@ public class TvWMShellModule { @WMSingleton @Provides static DisplayImeController provideDisplayImeController(IWindowManager wmService, - DisplayController displayController, @Main Executor mainExecutor, + DisplayController displayController, @ShellMainThread ShellExecutor shellMainExecutor, TransactionPool transactionPool) { - return new DisplayImeController(wmService, displayController, mainExecutor, + return new DisplayImeController(wmService, displayController, shellMainExecutor, transactionPool); } @@ -56,11 +55,12 @@ public class TvWMShellModule { @Provides static LegacySplitScreen provideSplitScreen(Context context, DisplayController displayController, SystemWindows systemWindows, - DisplayImeController displayImeController, @Main Handler handler, - TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, - SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener) { + DisplayImeController displayImeController, TransactionPool transactionPool, + ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, + TaskStackListenerImpl taskStackListener, Transitions transitions, + @ShellMainThread ShellExecutor mainExecutor) { return new LegacySplitScreenController(context, displayController, systemWindows, - displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue, - taskStackListener); + displayImeController, transactionPool, shellTaskOrganizer, syncQueue, + taskStackListener, transitions, mainExecutor); } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index 88cbddd4f088..572b15d5215a 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -36,9 +36,12 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.wm.shell.FullscreenTaskListener; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellCommandHandler; +import com.android.wm.shell.ShellCommandHandlerImpl; import com.android.wm.shell.ShellInit; +import com.android.wm.shell.ShellInitImpl; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.Transitions; +import com.android.wm.shell.TaskViewFactory; +import com.android.wm.shell.TaskViewFactoryController; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.BubbleController; @@ -58,6 +61,7 @@ import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; @@ -66,10 +70,9 @@ import com.android.wm.shell.pip.PipSurfaceTransactionHelper; 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.legacysplitscreen.LegacySplitScreen; +import com.android.wm.shell.transition.Transitions; import java.util.Optional; -import java.util.concurrent.TimeUnit; import dagger.BindsOptionalOf; import dagger.Module; @@ -158,7 +161,7 @@ public abstract class WMShellBaseModule { // Choreographer.getSfInstance() which returns a thread-local Choreographer instance // that uses the SF vsync handler.setProvider(new SfVsyncFrameCallbackProvider()); - }, 1, TimeUnit.SECONDS); + }); return handler; } catch (InterruptedException e) { throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e); @@ -173,14 +176,16 @@ public abstract class WMShellBaseModule { Optional<LegacySplitScreen> legacySplitScreenOptional, Optional<AppPairs> appPairsOptional, FullscreenTaskListener fullscreenTaskListener, - Transitions transitions) { - return new ShellInit(displayImeController, + Transitions transitions, + @ShellMainThread ShellExecutor shellMainExecutor) { + return ShellInitImpl.create(displayImeController, dragAndDropController, shellTaskOrganizer, legacySplitScreenOptional, appPairsOptional, fullscreenTaskListener, - transitions); + transitions, + shellMainExecutor); } /** @@ -195,9 +200,11 @@ public abstract class WMShellBaseModule { Optional<Pip> pipOptional, Optional<OneHanded> oneHandedOptional, Optional<HideDisplayCutout> hideDisplayCutout, - Optional<AppPairs> appPairsOptional) { - return Optional.of(new ShellCommandHandler(shellTaskOrganizer, legacySplitScreenOptional, - pipOptional, oneHandedOptional, hideDisplayCutout, appPairsOptional)); + Optional<AppPairs> appPairsOptional, + @ShellMainThread ShellExecutor shellMainExecutor) { + return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer, + legacySplitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout, + appPairsOptional, shellMainExecutor)); } @WMSingleton @@ -208,9 +215,9 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static DisplayController provideDisplayController(Context context, @Main Handler handler, - IWindowManager wmService) { - return new DisplayController(context, handler, wmService); + static DisplayController provideDisplayController(Context context, + IWindowManager wmService, @ShellMainThread ShellExecutor shellMainExecutor) { + return new DisplayController(context, wmService, shellMainExecutor); } @WMSingleton @@ -269,9 +276,9 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static SyncTransactionQueue provideSyncTransactionQueue(@Main Handler handler, - TransactionPool pool) { - return new SyncTransactionQueue(pool, handler); + static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool, + @ShellMainThread ShellExecutor shellMainExecutor) { + return new SyncTransactionQueue(pool, shellMainExecutor); } @WMSingleton @@ -288,10 +295,12 @@ public abstract class WMShellBaseModule { return new RootTaskDisplayAreaOrganizer(mainExecutor, context); } + // We currently dedupe multiple messages, so we use the shell main handler directly @WMSingleton @Provides - static TaskStackListenerImpl providerTaskStackListenerImpl(@Main Handler handler) { - return new TaskStackListenerImpl(handler); + static TaskStackListenerImpl providerTaskStackListenerImpl( + @ShellMainThread Handler shellMainHandler) { + return new TaskStackListenerImpl(shellMainHandler); } @BindsOptionalOf @@ -309,11 +318,12 @@ public abstract class WMShellBaseModule { WindowManagerShellWrapper windowManagerShellWrapper, LauncherApps launcherApps, UiEventLogger uiEventLogger, - @Main Handler mainHandler, - ShellTaskOrganizer organizer) { + ShellTaskOrganizer organizer, + @ShellMainThread ShellExecutor shellMainExecutor) { return Optional.of(BubbleController.create(context, null /* synchronizer */, floatingContentCoordinator, statusBarService, windowManager, - windowManagerShellWrapper, launcherApps, uiEventLogger, mainHandler, organizer)); + windowManagerShellWrapper, launcherApps, uiEventLogger, organizer, + shellMainExecutor)); } @WMSingleton @@ -335,6 +345,14 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides + static Optional<TaskViewFactory> provideTaskViewFactory(ShellTaskOrganizer shellTaskOrganizer, + @ShellMainThread ShellExecutor mainExecutor) { + return Optional.of(new TaskViewFactoryController(shellTaskOrganizer, mainExecutor) + .getTaskViewFactory()); + } + + @WMSingleton + @Provides static FullscreenTaskListener provideFullscreenTaskListener( SyncTransactionQueue syncQueue) { return new FullscreenTaskListener(syncQueue); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java index 3399a0bfc72e..e635e1708841 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java @@ -17,11 +17,9 @@ 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.WindowManagerShellWrapper; import com.android.wm.shell.apppairs.AppPairs; @@ -35,6 +33,8 @@ import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; 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.pip.Pip; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; @@ -46,11 +46,9 @@ import com.android.wm.shell.pip.phone.PhonePipMenuController; 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.legacysplitscreen.LegacySplitScreen; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; +import com.android.wm.shell.transition.Transitions; import java.util.Optional; -import java.util.concurrent.Executor; import dagger.Module; import dagger.Provides; @@ -64,9 +62,9 @@ public class WMShellModule { @WMSingleton @Provides static DisplayImeController provideDisplayImeController(IWindowManager wmService, - DisplayController displayController, @Main Executor mainExecutor, + DisplayController displayController, @ShellMainThread ShellExecutor shellMainExecutor, TransactionPool transactionPool) { - return new DisplayImeController(wmService, displayController, mainExecutor, + return new DisplayImeController(wmService, displayController, shellMainExecutor, transactionPool); } @@ -74,12 +72,13 @@ public class WMShellModule { @Provides static LegacySplitScreen provideLegacySplitScreen(Context context, DisplayController displayController, SystemWindows systemWindows, - DisplayImeController displayImeController, @Main Handler handler, - TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, - SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener) { + DisplayImeController displayImeController, TransactionPool transactionPool, + ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, + TaskStackListenerImpl taskStackListener, Transitions transitions, + @ShellMainThread ShellExecutor mainExecutor) { return new LegacySplitScreenController(context, displayController, systemWindows, - displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue, - taskStackListener); + displayImeController, transactionPool, shellTaskOrganizer, syncQueue, + taskStackListener, transitions, 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 6978ef4fb9ac..4e4c33a27da8 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.accessibility.IRemoteMagnificationAnimationCallback; 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 org.mockito.MockitoAnnotations; 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/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt index 5d81de6bce00..59c2b176f12e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt @@ -33,6 +33,7 @@ import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dump.DumpManager import com.android.systemui.tuner.TunerService import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -85,6 +86,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Mock private lateinit var sharedPrefsEditor: SharedPreferences.Editor @Mock private lateinit var mockContext: Context @Mock private lateinit var pendingIntent: PendingIntent + @Mock private lateinit var dumpManager: DumpManager @Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback> @@ -120,7 +122,7 @@ class MediaResumeListenerTest : SysuiTestCase() { executor = FakeExecutor(FakeSystemClock()) resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor, - tunerService, resumeBrowserFactory) + tunerService, resumeBrowserFactory, dumpManager) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) @@ -159,7 +161,7 @@ class MediaResumeListenerTest : SysuiTestCase() { // When listener is created, we do NOT register a user change listener val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService, - resumeBrowserFactory) + resumeBrowserFactory, dumpManager) listener.setManager(mediaDataManager) verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver), any(), any(), any()) diff --git a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceTileTest.java index 1d830030fc5b..bd6167b1f781 100644 --- a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceTileTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.app.people; +package com.android.systemui.people; import static com.google.common.truth.Truth.assertThat; @@ -39,6 +39,9 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.people.PeopleSpaceTile; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,9 +50,8 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) @SmallTest -public class PeopleSpaceTileTest { +public class PeopleSpaceTileTest extends SysuiTestCase { - private Context mContext; private final Drawable mDrawable = new ColorDrawable(Color.BLUE); private final Icon mIcon = PeopleSpaceTile.convertDrawableToIcon(mDrawable); @@ -58,7 +60,6 @@ public class PeopleSpaceTileTest { @Before public void setUp() { - mContext = InstrumentationRegistry.getContext(); MockitoAnnotations.initMocks(this); when(mLauncherApps.getShortcutIconDrawable(any(), eq(0))).thenReturn(mDrawable); } @@ -131,7 +132,7 @@ public class PeopleSpaceTileTest { PeopleSpaceTile tile = new PeopleSpaceTile.Builder( new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build(); // Automatically added by creating a ShortcutInfo. - assertThat(tile.getPackageName()).isEqualTo("com.android.frameworks.coretests"); + assertThat(tile.getPackageName()).isEqualTo("com.android.systemui.tests"); tile = new PeopleSpaceTile.Builder( new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).setPackageName( diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java index 644373c213af..e6cc10745806 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java @@ -32,7 +32,6 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.Person; -import android.app.people.PeopleSpaceTile; import android.appwidget.AppWidgetManager; import android.content.ContentResolver; import android.content.Context; diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java index df07b125e5fa..63ea7dd6f4a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java @@ -35,7 +35,6 @@ import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.Person; -import android.app.people.PeopleSpaceTile; import android.appwidget.AppWidgetManager; import android.content.Intent; import android.content.SharedPreferences; @@ -58,6 +57,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.appwidget.IAppWidgetService; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.people.PeopleSpaceTile; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; import com.android.systemui.statusbar.SbnBuilder; diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java index b3176ddeaf65..4d32a3b4077f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java @@ -40,8 +40,8 @@ import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.wm.shell.pip.Pip; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; +import com.android.wm.shell.pip.Pip; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java index f566880ee129..f2bf7aa3d842 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java @@ -85,8 +85,6 @@ public class ImageExporterTest extends SysuiTestCase { assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00", exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL)); - assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00", - exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED)); } @Test @@ -127,13 +125,6 @@ public class ImageExporterTest extends SysuiTestCase { exifInterface.getAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL)); assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "-05:00", exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL)); - - assertEquals("Exif " + ExifInterface.TAG_DATETIME_DIGITIZED, "2020:12:15 13:15:00", - exifInterface.getAttribute(ExifInterface.TAG_DATETIME_DIGITIZED)); - assertEquals("Exif " + ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, "000", - exifInterface.getAttribute(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED)); - assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_DIGITIZED, "-05:00", - exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED)); } } finally { if (decoded != null) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java index 6759c9075356..ced8428e6e6b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java @@ -176,7 +176,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = - new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data, + new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data, ShareTransition::new); Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(), @@ -204,7 +204,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = - new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data, + new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data, ShareTransition::new); Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(), @@ -232,7 +232,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = - new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data, + new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data, ShareTransition::new); Notification.Action deleteAction = task.createDeleteAction(mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java index 4aa730efa91e..c1c637129d85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java @@ -96,13 +96,13 @@ public class ScrollCaptureClientTest extends SysuiTestCase { Connection conn = mConnectionConsumer.getValue(); - conn.start(5, mSessionConsumer); + conn.start(mSessionConsumer); verify(mSessionConsumer, timeout(100)).accept(any(Session.class)); Session session = mSessionConsumer.getValue(); - Rect request = new Rect(0, 0, session.getMaxTileWidth(), session.getMaxTileHeight()); + Rect request = new Rect(0, 0, session.getPageWidth(), session.getTileHeight()); - session.requestTile(request, mResultConsumer); + session.requestTile(0, mResultConsumer); verify(mResultConsumer, timeout(100)).accept(any(CaptureResult.class)); CaptureResult result = mResultConsumer.getValue(); 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 000000000000..4068f93f5ee3 --- /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/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 230aeab4183c..ccc2eb328a04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -89,6 +89,8 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.Bubble; @@ -100,6 +102,7 @@ import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubbleStackView; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.ShellExecutor; import com.google.common.collect.ImmutableList; @@ -203,6 +206,8 @@ public class BubblesTest extends SysuiTestCase { private WindowManagerShellWrapper mWindowManagerShellWrapper; @Mock private BubbleLogger mBubbleLogger; + @Mock + private ShellTaskOrganizer mShellTaskOrganizer; private TestableBubblePositioner mPositioner; @@ -268,6 +273,7 @@ public class BubblesTest extends SysuiTestCase { ); when(mFeatureFlagsOldPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(false); + when(mShellTaskOrganizer.getExecutor()).thenReturn(new FakeExecutor(new FakeSystemClock())); mBubbleController = new TestableBubbleController( mContext, mBubbleData, @@ -278,9 +284,9 @@ public class BubblesTest extends SysuiTestCase { mWindowManagerShellWrapper, mLauncherApps, mBubbleLogger, - mock(Handler.class), - mock(ShellTaskOrganizer.class), - mPositioner); + mShellTaskOrganizer, + mPositioner, + mock(ShellExecutor.class)); mBubbleController.setExpandListener(mBubbleExpandListener); mBubblesManager = new BubblesManager( diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index bbcc30abe315..00f4e3a3f144 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -83,6 +83,8 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.BubbleData; @@ -93,6 +95,7 @@ import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubbleStackView; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.ShellExecutor; import org.junit.Before; import org.junit.Ignore; @@ -185,6 +188,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { private WindowManagerShellWrapper mWindowManagerShellWrapper; @Mock private BubbleLogger mBubbleLogger; + @Mock + private ShellTaskOrganizer mShellTaskOrganizer; private TestableBubblePositioner mPositioner; @@ -236,6 +241,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mock(Handler.class) ); when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true); + when(mShellTaskOrganizer.getExecutor()).thenReturn(new FakeExecutor(new FakeSystemClock())); mBubbleController = new TestableBubbleController( mContext, mBubbleData, @@ -246,9 +252,9 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mWindowManagerShellWrapper, mLauncherApps, mBubbleLogger, - mock(Handler.class), - mock(ShellTaskOrganizer.class), - mPositioner); + mShellTaskOrganizer, + mPositioner, + mock(ShellExecutor.class)); mBubbleController.setExpandListener(mBubbleExpandListener); mBubblesManager = new BubblesManager( diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java index fd39b6e5b1c8..3f918e8b8633 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java @@ -30,6 +30,7 @@ import com.android.wm.shell.bubbles.BubbleDataRepository; import com.android.wm.shell.bubbles.BubbleLogger; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.ShellExecutor; /** * Testable BubbleController subclass that immediately synchronizes surfaces. @@ -46,12 +47,12 @@ public class TestableBubbleController extends BubbleController { WindowManagerShellWrapper windowManagerShellWrapper, LauncherApps launcherApps, BubbleLogger bubbleLogger, - Handler mainHandler, ShellTaskOrganizer shellTaskOrganizer, - BubblePositioner positioner) { + BubblePositioner positioner, + ShellExecutor shellMainExecutor) { super(context, data, Runnable::run, floatingContentCoordinator, dataRepository, statusBarService, windowManager, windowManagerShellWrapper, launcherApps, - bubbleLogger, mainHandler, shellTaskOrganizer, positioner); + bubbleLogger, shellTaskOrganizer, positioner, shellMainExecutor); setInflateSynchronously(true); } } 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 73d87b0d4596..31bf7120900f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -35,12 +35,12 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tracing.ProtoTracer; import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedGestureHandler; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.phone.PipTouchHandler; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import org.junit.Before; import org.junit.Test; diff --git a/packages/overlays/OneHandedModeGesturalOverlay/res/values/dimens.xml b/packages/overlays/OneHandedModeGesturalOverlay/res/values/dimens.xml index 738256551e9c..3986119c38f2 100644 --- a/packages/overlays/OneHandedModeGesturalOverlay/res/values/dimens.xml +++ b/packages/overlays/OneHandedModeGesturalOverlay/res/values/dimens.xml @@ -18,5 +18,5 @@ --> <resources> <!-- The height of the bottom navigation gesture area. --> - <dimen name="navigation_bar_gesture_height">80dp</dimen> + <dimen name="navigation_bar_gesture_larger_height">80dp</dimen> </resources> diff --git a/proto/src/OWNERS b/proto/src/OWNERS index e7ddf8691463..b456ba60d086 100644 --- a/proto/src/OWNERS +++ b/proto/src/OWNERS @@ -1,2 +1,3 @@ per-file gnss.proto = file:/services/core/java/com/android/server/location/OWNERS per-file wifi.proto = file:/wifi/OWNERS +per-file camera.proto = file:/services/core/java/com/android/server/camera/OWNERS diff --git a/services/Android.bp b/services/Android.bp index 3447b5cbea52..da24719c0f68 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -30,6 +30,7 @@ filegroup { ":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 @@ java_library { "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/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index c6919ad24572..9aa0aed06892 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -508,6 +508,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } it.remove(); userState.getBindingServicesLocked().remove(comp); + userState.getCrashedServicesLocked().remove(comp); persistComponentNamesToSettingLocked( Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userState.mEnabledServices, userId); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 8a27acce03c3..728e82900730 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.CheckResult; 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.FeatureInfo; 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.io.PrintWriter; 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 @@ public class CompanionDeviceManagerService extends SystemService implements Bind @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 @@ public class CompanionDeviceManagerService extends SystemService implements Bind 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 @@ public class CompanionDeviceManagerService extends SystemService implements Bind 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 @@ public class CompanionDeviceManagerService extends SystemService implements Bind 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 @@ public class CompanionDeviceManagerService extends SystemService implements Bind 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)); } } @@ -919,9 +929,10 @@ public class CompanionDeviceManagerService extends SystemService implements Bind break; case "associate": { + int userId = getNextArgInt(); String pkg = getNextArgRequired(); String address = getNextArgRequired(); - addAssociation(new Association(getNextArgInt(), address, pkg, null, false)); + addAssociation(new Association(userId, address, pkg, null, false)); } break; diff --git a/services/core/Android.bp b/services/core/Android.bp index 6ce334bae333..019d8c558aef 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -141,7 +141,7 @@ java_library_static { "capture_state_listener-aidl-java", "dnsresolver_aidl_interface-java", "icu4j_calendar_astronomer", - "netd_aidl_interfaces-platform-java", + "netd-client", "overlayable_policy_aidl-java", "SurfaceFlingerProperties", "com.android.sysprop.watchdog", diff --git a/services/core/java/android/power/OWNERS b/services/core/java/android/power/OWNERS new file mode 100644 index 000000000000..4068e2bc03b7 --- /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 397eeb23396c..020c17a77084 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -620,7 +620,7 @@ public class ConnectivityService extends IConnectivityManager.Stub 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( @@ -1238,6 +1238,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } private synchronized int nextNetworkRequestId() { + // TODO: Consider handle wrapping and exclude {@link NetworkRequest#REQUEST_ID_NONE} if + // doing that. return mNextNetworkRequestId++; } @@ -1329,15 +1331,20 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * 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 +1382,13 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * 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 +1448,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } } 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 +1521,7 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 +4479,8 @@ public class ConnectivityService extends IConnectivityManager.Stub 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); @@ -7065,11 +7074,11 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 f701688b2b7e..0779f7117d82 100644 --- a/services/core/java/com/android/server/ConnectivityServiceInitializer.java +++ b/services/core/java/com/android/server/ConnectivityServiceInitializer.java @@ -35,6 +35,8 @@ public final class ConnectivityServiceInitializer extends SystemService { 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/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java index ea1ac0c3fddc..2906ceebca58 100644 --- a/services/core/java/com/android/server/IntentResolver.java +++ b/services/core/java/com/android/server/IntentResolver.java @@ -839,14 +839,22 @@ public abstract class IntentResolver<F, R extends Object> { } }; - // Make <this> a copy of <orig>. The presumption is that <this> is empty. - protected void doCopy(IntentResolver orig) { + // Make <this> a copy of <orig>. The presumption is that <this> is empty but all + // arrays are cleared out explicitly, just to be sure. + protected void copyFrom(IntentResolver orig) { + mFilters.clear(); mFilters.addAll(orig.mFilters); + mTypeToFilter.clear(); mTypeToFilter.putAll(orig.mTypeToFilter); + mBaseTypeToFilter.clear(); mBaseTypeToFilter.putAll(orig.mBaseTypeToFilter); + mWildTypeToFilter.clear(); mWildTypeToFilter.putAll(orig.mWildTypeToFilter); + mSchemeToFilter.clear(); mSchemeToFilter.putAll(orig.mSchemeToFilter); + mActionToFilter.clear(); mActionToFilter.putAll(orig.mActionToFilter); + mTypedActionToFilter.clear(); mTypedActionToFilter.putAll(orig.mTypedActionToFilter); } diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 99a1d86d244e..8b506bac4a85 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -54,9 +54,13 @@ import libcore.io.IoUtils; 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 @@ public class PackageWatchdog { 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 @@ public class PackageWatchdog { } if (currentObserverToNotify != null) { mBootThreshold.setMitigationCount(mitigationCount); + mBootThreshold.saveMitigationCountToMetadata(); currentObserverToNotify.executeBootLoopMitigation(mitigationCount); } } @@ -1700,9 +1710,31 @@ public class PackageWatchdog { 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 a1cf8162f0e9..db36e62e44a3 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.Build; 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 @@ public class RescueParty { @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 @@ public class RescueParty { @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 @@ public class RescueParty { } /** - * 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 @@ public class RescueParty { 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 @@ public class RescueParty { // 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 @@ public class RescueParty { 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 @@ public class RescueParty { } } }; - Thread thread = new Thread(runnable); + thread = new Thread(runnable); thread.start(); break; } @@ -433,6 +468,7 @@ public class RescueParty { 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 @@ public class RescueParty { 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/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index 010213453940..9ba71dc5f4f7 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -20,12 +20,14 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.UserHandle.USER_SYSTEM; import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationChannel; @@ -49,12 +51,15 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.ShellCommand; +import android.os.UserHandle; import android.service.SensorPrivacyIndividualEnabledSensorProto; import android.service.SensorPrivacyServiceDumpProto; +import android.service.SensorPrivacyUserProto; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.Log; +import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; @@ -63,9 +68,11 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FunctionalUtils; import com.android.internal.util.XmlUtils; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.internal.util.function.pooled.PooledLambda; +import com.android.server.pm.UserManagerInternal; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -84,9 +91,19 @@ public final class SensorPrivacyService extends SystemService { private static final String TAG = "SensorPrivacyService"; + /** Version number indicating compatibility parsing the persisted file */ + private static final int CURRENT_PERSISTENCE_VERSION = 1; + /** Version number indicating the persisted data needs upgraded to match new internal data + * structures and features */ + private static final int CURRENT_VERSION = 1; + private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml"; private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy"; + private static final String XML_TAG_USER = "user"; private static final String XML_TAG_INDIVIDUAL_SENSOR_PRIVACY = "individual-sensor-privacy"; + private static final String XML_ATTRIBUTE_ID = "id"; + private static final String XML_ATTRIBUTE_PERSISTENCE_VERSION = "persistence-version"; + private static final String XML_ATTRIBUTE_VERSION = "version"; private static final String XML_ATTRIBUTE_ENABLED = "enabled"; private static final String XML_ATTRIBUTE_SENSOR = "sensor"; @@ -96,10 +113,18 @@ public final class SensorPrivacyService extends SystemService { private static final String EXTRA_SENSOR = SensorPrivacyService.class.getName() + ".extra.sensor"; + // These are associated with fields that existed for older persisted versions of files + private static final int VER0_ENABLED = 0; + private static final int VER0_INDIVIDUAL_ENABLED = 1; + private static final int VER1_ENABLED = 0; + private static final int VER1_INDIVIDUAL_ENABLED = 1; + private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl; + private final UserManagerInternal mUserManagerInternal; public SensorPrivacyService(Context context) { super(context); + mUserManagerInternal = getLocalService(UserManagerInternal.class); mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context); } @@ -117,8 +142,9 @@ public final class SensorPrivacyService extends SystemService { @GuardedBy("mLock") private final AtomicFile mAtomicFile; @GuardedBy("mLock") - private boolean mEnabled; - private SparseBooleanArray mIndividualEnabled = new SparseBooleanArray(); + private SparseBooleanArray mEnabled = new SparseBooleanArray(); + @GuardedBy("mLock") + private SparseArray<SparseBooleanArray> mIndividualEnabled = new SparseArray<>(); SensorPrivacyServiceImpl(Context context) { mContext = context; @@ -127,7 +153,9 @@ public final class SensorPrivacyService extends SystemService { SENSOR_PRIVACY_XML_FILE); mAtomicFile = new AtomicFile(sensorPrivacyFile); synchronized (mLock) { - readPersistedSensorPrivacyStateLocked(); + if (readPersistedSensorPrivacyStateLocked()) { + persistSensorPrivacyStateLocked(); + } } int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_CAMERA}; @@ -138,7 +166,8 @@ public final class SensorPrivacyService extends SystemService { mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - setIndividualSensorPrivacy(intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), false); + setIndividualSensorPrivacy(intent.getIntExtra(Intent.EXTRA_USER_ID, -1), + intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), false); } }, new IntentFilter(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY)); } @@ -174,7 +203,8 @@ public final class SensorPrivacyService extends SystemService { * @param sensor The sensor that is attempting to be used */ private void onSensorUseStarted(int uid, String packageName, int sensor) { - if (!isIndividualSensorPrivacyEnabled(sensor)) { + int userId = UserHandle.getUserId(uid); + if (!isIndividualSensorPrivacyEnabled(userId, sensor)) { return; } @@ -216,7 +246,8 @@ public final class SensorPrivacyService extends SystemService { PendingIntent.getBroadcast(mContext, sensor, new Intent(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY) .setPackage(mContext.getPackageName()) - .putExtra(EXTRA_SENSOR, sensor), + .putExtra(EXTRA_SENSOR, sensor) + .putExtra(Intent.EXTRA_USER_ID, userId), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT)) .build()) @@ -229,18 +260,27 @@ public final class SensorPrivacyService extends SystemService { */ @Override public void setSensorPrivacy(boolean enable) { + // Keep the state consistent between all users to make it a single global state + forAllUsers(userId -> setSensorPrivacy(userId, enable)); + } + + private void setSensorPrivacy(@UserIdInt int userId, boolean enable) { enforceSensorPrivacyPermission(); synchronized (mLock) { - mEnabled = enable; + mEnabled.put(userId, enable); persistSensorPrivacyStateLocked(); } mHandler.onSensorPrivacyChanged(enable); } - public void setIndividualSensorPrivacy(int sensor, boolean enable) { + @Override + public void setIndividualSensorPrivacy(@UserIdInt int userId, int sensor, boolean enable) { enforceSensorPrivacyPermission(); synchronized (mLock) { - mIndividualEnabled.put(sensor, enable); + SparseBooleanArray userIndividualEnabled = mIndividualEnabled.get(userId, + new SparseBooleanArray()); + userIndividualEnabled.put(sensor, enable); + mIndividualEnabled.put(userId, userIndividualEnabled); if (!enable) { // Remove any notifications prompting the user to disable sensory privacy @@ -249,9 +289,20 @@ public final class SensorPrivacyService extends SystemService { notificationManager.cancel(sensor); } - persistSensorPrivacyState(); } + mHandler.onSensorPrivacyChanged(userId, sensor, enable); + } + + @Override + public void setIndividualSensorPrivacyForProfileGroup(@UserIdInt int userId, int sensor, + boolean enable) { + int parentId = mUserManagerInternal.getProfileParentId(userId); + forAllUsers(userId2 -> { + if (parentId == mUserManagerInternal.getProfileParentId(userId2)) { + setIndividualSensorPrivacy(userId2, sensor, enable); + } + }); } /** @@ -273,53 +324,179 @@ public final class SensorPrivacyService extends SystemService { */ @Override public boolean isSensorPrivacyEnabled() { + return isSensorPrivacyEnabled(USER_SYSTEM); + } + + private boolean isSensorPrivacyEnabled(@UserIdInt int userId) { synchronized (mLock) { - return mEnabled; + return mEnabled.get(userId, false); } } @Override - public boolean isIndividualSensorPrivacyEnabled(int sensor) { + public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) { synchronized (mLock) { - return mIndividualEnabled.get(sensor, false); + SparseBooleanArray states = mIndividualEnabled.get(userId); + if (states == null) { + return false; + } + return states.get(sensor, false); } } /** * Returns the state of sensor privacy from persistent storage. */ - private void readPersistedSensorPrivacyStateLocked() { + private boolean readPersistedSensorPrivacyStateLocked() { // if the file does not exist then sensor privacy has not yet been enabled on // the device. - if (!mAtomicFile.exists()) { - return; - } - try (FileInputStream inputStream = mAtomicFile.openRead()) { - TypedXmlPullParser parser = Xml.resolvePullParser(inputStream); - XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY); - parser.next(); - mEnabled = parser.getAttributeBoolean(null, XML_ATTRIBUTE_ENABLED, false); - - XmlUtils.nextElement(parser); - while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { - String tagName = parser.getName(); - if (XML_TAG_INDIVIDUAL_SENSOR_PRIVACY.equals(tagName)) { - int sensor = XmlUtils.readIntAttribute(parser, XML_ATTRIBUTE_SENSOR); - boolean enabled = XmlUtils.readBooleanAttribute(parser, - XML_ATTRIBUTE_ENABLED); - mIndividualEnabled.put(sensor, enabled); - XmlUtils.skipCurrentTag(parser); - } else { + + SparseArray<Object> map = new SparseArray<>(); + int version = -1; + + if (mAtomicFile.exists()) { + try (FileInputStream inputStream = mAtomicFile.openRead()) { + TypedXmlPullParser parser = Xml.resolvePullParser(inputStream); + XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY); + final int persistenceVersion = parser.getAttributeInt(null, + XML_ATTRIBUTE_PERSISTENCE_VERSION, 0); + + // Use inline string literals for xml tags/attrs when parsing old versions since + // these should never be changed even with refactorings. + if (persistenceVersion == 0) { + boolean enabled = parser.getAttributeBoolean(null, "enabled", false); + SparseBooleanArray individualEnabled = new SparseBooleanArray(); + version = 0; + XmlUtils.nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + String tagName = parser.getName(); + if ("individual-sensor-privacy".equals(tagName)) { + int sensor = XmlUtils.readIntAttribute(parser, "sensor"); + boolean indEnabled = XmlUtils.readBooleanAttribute(parser, + "enabled"); + individualEnabled.put(sensor, indEnabled); + XmlUtils.skipCurrentTag(parser); + } else { + XmlUtils.nextElement(parser); + } + } + map.put(VER0_ENABLED, enabled); + map.put(VER0_INDIVIDUAL_ENABLED, individualEnabled); + } else if (persistenceVersion == CURRENT_PERSISTENCE_VERSION) { + SparseBooleanArray enabled = new SparseBooleanArray(); + SparseArray<SparseBooleanArray> individualEnabled = new SparseArray<>(); + version = parser.getAttributeInt(null, + XML_ATTRIBUTE_VERSION, 1); + + int currentUserId = -1; + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + XmlUtils.nextElement(parser); + String tagName = parser.getName(); + if (XML_TAG_USER.equals(tagName)) { + currentUserId = parser.getAttributeInt(null, XML_ATTRIBUTE_ID); + boolean isEnabled = parser.getAttributeBoolean(null, + XML_ATTRIBUTE_ENABLED); + if (enabled.indexOfKey(currentUserId) >= 0) { + Log.e(TAG, "User listed multiple times in file.", + new RuntimeException()); + mAtomicFile.delete(); + version = -1; + break; + } + + if (mUserManagerInternal.getUserInfo(currentUserId) == null) { + // User may no longer exist, skip this user + currentUserId = -1; + continue; + } + + enabled.put(currentUserId, isEnabled); + } + if (XML_TAG_INDIVIDUAL_SENSOR_PRIVACY.equals(tagName)) { + if (mUserManagerInternal.getUserInfo(currentUserId) == null) { + // User may no longer exist or isn't set + continue; + } + int sensor = parser.getAttributeIndex(null, XML_ATTRIBUTE_SENSOR); + boolean isEnabled = parser.getAttributeBoolean(null, + XML_ATTRIBUTE_ENABLED); + SparseBooleanArray userIndividualEnabled = individualEnabled.get( + currentUserId, new SparseBooleanArray()); + + userIndividualEnabled.put(sensor, isEnabled); + individualEnabled.put(currentUserId, userIndividualEnabled); + } + } + + map.put(VER1_ENABLED, enabled); + map.put(VER1_INDIVIDUAL_ENABLED, individualEnabled); + } else { + Log.e(TAG, "Unknown persistence version: " + persistenceVersion + + ". Deleting.", + new RuntimeException()); + mAtomicFile.delete(); + version = -1; + } + + } catch (IOException | XmlPullParserException e) { + Log.e(TAG, "Caught an exception reading the state from storage: ", e); + // Delete the file to prevent the same error on subsequent calls and assume + // sensor privacy is not enabled. + mAtomicFile.delete(); + version = -1; + } + } + + return upgradeAndInit(version, map); + } + + private boolean upgradeAndInit(int version, SparseArray map) { + if (version == -1) { + // New file, default state for current version goes here. + mEnabled = new SparseBooleanArray(); + mIndividualEnabled = new SparseArray<>(); + forAllUsers(userId -> mEnabled.put(userId, false)); + forAllUsers(userId -> mIndividualEnabled.put(userId, new SparseBooleanArray())); + return true; + } + boolean upgraded = false; + final int[] users = getLocalService(UserManagerInternal.class).getUserIds(); + if (version == 0) { + final boolean enabled = (boolean) map.get(VER0_ENABLED); + final SparseBooleanArray individualEnabled = + (SparseBooleanArray) map.get(VER0_INDIVIDUAL_ENABLED); + + final SparseBooleanArray perUserEnabled = new SparseBooleanArray(); + final SparseArray<SparseBooleanArray> perUserIndividualEnabled = + new SparseArray<>(); + + // Copy global state to each user + for (int i = 0; i < users.length; i++) { + int user = users[i]; + perUserEnabled.put(user, enabled); + SparseBooleanArray userIndividualSensorEnabled = new SparseBooleanArray(); + perUserIndividualEnabled.put(user, userIndividualSensorEnabled); + for (int j = 0; j < individualEnabled.size(); j++) { + final int sensor = individualEnabled.keyAt(j); + final boolean isSensorEnabled = individualEnabled.valueAt(j); + userIndividualSensorEnabled.put(sensor, isSensorEnabled); } } - } catch (IOException | XmlPullParserException e) { - Log.e(TAG, "Caught an exception reading the state from storage: ", e); - // Delete the file to prevent the same error on subsequent calls and assume sensor - // privacy is not enabled. - mAtomicFile.delete(); + map.clear(); + map.put(VER1_ENABLED, perUserEnabled); + map.put(VER1_INDIVIDUAL_ENABLED, perUserIndividualEnabled); + + version = 1; + upgraded = true; + } + if (version == CURRENT_VERSION) { + mEnabled = (SparseBooleanArray) map.get(VER1_ENABLED); + mIndividualEnabled = + (SparseArray<SparseBooleanArray>) map.get(VER1_INDIVIDUAL_ENABLED); } + return upgraded; } /** @@ -338,16 +515,29 @@ public final class SensorPrivacyService extends SystemService { TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream); serializer.startDocument(null, true); serializer.startTag(null, XML_TAG_SENSOR_PRIVACY); - serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, mEnabled); - int numIndividual = mIndividualEnabled.size(); - for (int i = 0; i < numIndividual; i++) { - serializer.startTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY); - int sensor = mIndividualEnabled.keyAt(i); - boolean enabled = mIndividualEnabled.valueAt(i); - serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR, sensor); - serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled); - serializer.endTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY); - } + serializer.attributeInt( + null, XML_ATTRIBUTE_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION); + serializer.attributeInt(null, XML_ATTRIBUTE_VERSION, CURRENT_VERSION); + forAllUsers(userId -> { + serializer.startTag(null, XML_TAG_USER); + serializer.attributeInt(null, XML_ATTRIBUTE_ID, userId); + serializer.attributeBoolean( + null, XML_ATTRIBUTE_ENABLED, isSensorPrivacyEnabled(userId)); + + SparseBooleanArray individualEnabled = + mIndividualEnabled.get(userId, new SparseBooleanArray()); + int numIndividual = individualEnabled.size(); + for (int i = 0; i < numIndividual; i++) { + serializer.startTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY); + int sensor = individualEnabled.keyAt(i); + boolean enabled = individualEnabled.valueAt(i); + serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR, sensor); + serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled); + serializer.endTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY); + } + serializer.endTag(null, XML_TAG_USER); + + }); serializer.endTag(null, XML_TAG_SENSOR_PRIVACY); serializer.endDocument(); mAtomicFile.finishWrite(outputStream); @@ -369,6 +559,18 @@ public final class SensorPrivacyService extends SystemService { } /** + * Registers a listener to be notified when the sensor privacy state changes. + */ + @Override + public void addIndividualSensorPrivacyListener(int userId, int sensor, + ISensorPrivacyListener listener) { + if (listener == null) { + throw new NullPointerException("listener cannot be null"); + } + mHandler.addListener(userId, sensor, listener); + } + + /** * Unregisters a listener from sensor privacy state change notifications. */ @Override @@ -422,22 +624,32 @@ public final class SensorPrivacyService extends SystemService { */ private void dump(@NonNull DualDumpOutputStream dumpStream) { synchronized (mLock) { - dumpStream.write("is_enabled", SensorPrivacyServiceDumpProto.IS_ENABLED, mEnabled); - int numIndividualEnabled = mIndividualEnabled.size(); - for (int i = 0; i < numIndividualEnabled; i++) { - long token = dumpStream.start("individual_enabled_sensor", - SensorPrivacyServiceDumpProto.INDIVIDUAL_ENABLED_SENSOR); - - dumpStream.write("sensor", - SensorPrivacyIndividualEnabledSensorProto.SENSOR, - mIndividualEnabled.keyAt(i)); - dumpStream.write("is_enabled", - SensorPrivacyIndividualEnabledSensorProto.IS_ENABLED, - mIndividualEnabled.valueAt(i)); - - dumpStream.end(token); - } + forAllUsers(userId -> { + long userToken = dumpStream.start("users", SensorPrivacyServiceDumpProto.USER); + dumpStream.write("user_id", SensorPrivacyUserProto.USER_ID, userId); + dumpStream.write("is_enabled", SensorPrivacyUserProto.IS_ENABLED, + mEnabled.get(userId, false)); + + SparseBooleanArray individualEnabled = mIndividualEnabled.get(userId); + if (individualEnabled != null) { + int numIndividualEnabled = individualEnabled.size(); + for (int i = 0; i < numIndividualEnabled; i++) { + long individualToken = dumpStream.start("individual_enabled_sensor", + SensorPrivacyUserProto.INDIVIDUAL_ENABLED_SENSOR); + + dumpStream.write("sensor", + SensorPrivacyIndividualEnabledSensorProto.SENSOR, + individualEnabled.keyAt(i)); + dumpStream.write("is_enabled", + SensorPrivacyIndividualEnabledSensorProto.IS_ENABLED, + individualEnabled.valueAt(i)); + + dumpStream.end(individualToken); + } + } + dumpStream.end(userToken); + }); } dumpStream.flush(); @@ -477,30 +689,32 @@ public final class SensorPrivacyService extends SystemService { return handleDefaultCommands(cmd); } + int userId = Integer.parseInt(getNextArgRequired()); + final PrintWriter pw = getOutPrintWriter(); switch (cmd) { case "enable" : { - int sensor = sensorStrToId(getNextArg()); + int sensor = sensorStrToId(getNextArgRequired()); if (sensor == UNKNOWN) { pw.println("Invalid sensor"); return -1; } - setIndividualSensorPrivacy(sensor, true); + setIndividualSensorPrivacy(userId, sensor, true); } break; case "disable" : { - int sensor = sensorStrToId(getNextArg()); + int sensor = sensorStrToId(getNextArgRequired()); if (sensor == UNKNOWN) { pw.println("Invalid sensor"); return -1; } - setIndividualSensorPrivacy(sensor, false); + setIndividualSensorPrivacy(userId, sensor, false); } break; case "reset": { - int sensor = sensorStrToId(getNextArg()); + int sensor = sensorStrToId(getNextArgRequired()); if (sensor == UNKNOWN) { pw.println("Invalid sensor"); return -1; @@ -509,7 +723,11 @@ public final class SensorPrivacyService extends SystemService { enforceSensorPrivacyPermission(); synchronized (mLock) { - mIndividualEnabled.delete(sensor); + SparseBooleanArray individualEnabled = + mIndividualEnabled.get(userId); + if (individualEnabled != null) { + individualEnabled.delete(sensor); + } persistSensorPrivacyState(); } } @@ -530,13 +748,13 @@ public final class SensorPrivacyService extends SystemService { pw.println(" help"); pw.println(" Print this help text."); pw.println(""); - pw.println(" enable SENSOR"); + pw.println(" enable USER_ID SENSOR"); pw.println(" Enable privacy for a certain sensor."); pw.println(""); - pw.println(" disable SENSOR"); + pw.println(" disable USER_ID SENSOR"); pw.println(" Disable privacy for a certain sensor."); pw.println(""); - pw.println(" reset SENSOR"); + pw.println(" reset USER_ID SENSOR"); pw.println(" Reset privacy state for a certain sensor."); pw.println(""); } @@ -555,6 +773,9 @@ public final class SensorPrivacyService extends SystemService { @GuardedBy("mListenerLock") private final RemoteCallbackList<ISensorPrivacyListener> mListeners = new RemoteCallbackList<>(); + @GuardedBy("mListenerLock") + private final SparseArray<SparseArray<RemoteCallbackList<ISensorPrivacyListener>>> + mIndividualSensorListeners = new SparseArray<>(); private final ArrayMap<ISensorPrivacyListener, DeathRecipient> mDeathRecipients; private final Context mContext; @@ -572,6 +793,14 @@ public final class SensorPrivacyService extends SystemService { mSensorPrivacyServiceImpl)); } + public void onSensorPrivacyChanged(int userId, int sensor, boolean enabled) { + sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged, + this, userId, sensor, enabled)); + sendMessage( + PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState, + mSensorPrivacyServiceImpl)); + } + public void addListener(ISensorPrivacyListener listener) { synchronized (mListenerLock) { DeathRecipient deathRecipient = new DeathRecipient(listener); @@ -580,6 +809,25 @@ public final class SensorPrivacyService extends SystemService { } } + public void addListener(int userId, int sensor, ISensorPrivacyListener listener) { + synchronized (mListenerLock) { + DeathRecipient deathRecipient = new DeathRecipient(listener); + mDeathRecipients.put(listener, deathRecipient); + SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser = + mIndividualSensorListeners.get(userId); + if (listenersForUser == null) { + listenersForUser = new SparseArray<>(); + mIndividualSensorListeners.put(userId, listenersForUser); + } + RemoteCallbackList<ISensorPrivacyListener> listeners = listenersForUser.get(sensor); + if (listeners == null) { + listeners = new RemoteCallbackList<>(); + listenersForUser.put(sensor, listeners); + } + listeners.register(listener); + } + } + public void removeListener(ISensorPrivacyListener listener) { synchronized (mListenerLock) { DeathRecipient deathRecipient = mDeathRecipients.remove(listener); @@ -587,6 +835,12 @@ public final class SensorPrivacyService extends SystemService { deathRecipient.destroy(); } mListeners.unregister(listener); + for (int i = 0, numUsers = mIndividualSensorListeners.size(); i < numUsers; i++) { + for (int j = 0, numListeners = mIndividualSensorListeners.valueAt(i).size(); + j < numListeners; j++) { + mIndividualSensorListeners.valueAt(i).valueAt(j).unregister(listener); + } + } } } @@ -602,6 +856,28 @@ public final class SensorPrivacyService extends SystemService { } mListeners.finishBroadcast(); } + + public void handleSensorPrivacyChanged(int userId, int sensor, boolean enabled) { + SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser = + mIndividualSensorListeners.get(userId); + if (listenersForUser == null) { + return; + } + RemoteCallbackList<ISensorPrivacyListener> listeners = listenersForUser.get(sensor); + if (listeners == null) { + return; + } + final int count = listeners.beginBroadcast(); + for (int i = 0; i < count; i++) { + ISensorPrivacyListener listener = listeners.getBroadcastItem(i); + try { + listener.onSensorPrivacyChanged(enabled); + } catch (RemoteException e) { + Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", e); + } + } + listeners.finishBroadcast(); + } } private final class DeathRecipient implements IBinder.DeathRecipient { @@ -628,4 +904,11 @@ public final class SensorPrivacyService extends SystemService { } } } + + private void forAllUsers(FunctionalUtils.ThrowingConsumer<Integer> c) { + int[] userIds = mUserManagerInternal.getUserIds(); + for (int i = 0; i < userIds.length; i++) { + c.accept(userIds[i]); + } + } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index c476666771c9..b76f32788f6c 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -310,7 +310,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private final LocalLog mLocalLog = new LocalLog(200); - private final LocalLog mListenLog = new LocalLog(00); + private final LocalLog mListenLog = new LocalLog(200); private List<PhysicalChannelConfig> mPhysicalChannelConfigs; @@ -2455,7 +2455,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("local logs:"); pw.increaseIndent(); mLocalLog.dump(fd, pw, args); + pw.decreaseIndent(); pw.println("listen logs:"); + pw.increaseIndent(); mListenLog.dump(fd, pw, args); pw.decreaseIndent(); pw.println("registrations: count=" + recordCount); diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 74e38510770b..c191a78aad0e 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -16,13 +16,15 @@ package com.android.server; +import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; + import static java.util.Objects.requireNonNull; import android.annotation.NonNull; +import android.app.AppOpsManager; import android.content.Context; import android.net.ConnectivityManager; -import android.net.NetworkProvider; -import android.net.NetworkRequest; import android.net.vcn.IVcnManagementService; import android.net.vcn.VcnConfig; import android.os.Binder; @@ -43,6 +45,10 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.server.vcn.TelephonySubscriptionTracker; +import com.android.server.vcn.Vcn; +import com.android.server.vcn.VcnContext; +import com.android.server.vcn.VcnNetworkProvider; import com.android.server.vcn.util.PersistableBundleUtils; import java.io.IOException; @@ -51,6 +57,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; /** * VcnManagementService manages Virtual Carrier Network profiles and lifecycles. @@ -115,6 +122,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { @VisibleForTesting(visibility = Visibility.PRIVATE) static final String VCN_CONFIG_FILE = "/data/system/vcn/configs.xml"; + // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30); + /* Binder context for this service */ @NonNull private final Context mContext; @NonNull private final Dependencies mDeps; @@ -122,11 +133,23 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private final Looper mLooper; @NonNull private final Handler mHandler; @NonNull private final VcnNetworkProvider mNetworkProvider; + @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb; + @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker; + @NonNull private final VcnContext mVcnContext; @GuardedBy("mLock") @NonNull private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>(); + @GuardedBy("mLock") + @NonNull + private final Map<ParcelUuid, Vcn> mVcns = new ArrayMap<>(); + + @GuardedBy("mLock") + @NonNull + private TelephonySubscriptionSnapshot mLastSnapshot = + TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT; + @NonNull private final Object mLock = new Object(); @NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper; @@ -139,8 +162,12 @@ public class VcnManagementService extends IVcnManagementService.Stub { mLooper = mDeps.getLooper(); mHandler = new Handler(mLooper); mNetworkProvider = new VcnNetworkProvider(mContext, mLooper); + mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback(); + mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker( + mContext, mLooper, mTelephonySubscriptionTrackerCb); mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE); + mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider); // Run on handler to ensure I/O does not block system server startup mHandler.post(() -> { @@ -174,7 +201,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { mConfigs.put(entry.getKey(), entry.getValue()); } } - // TODO: Trigger re-evaluation of active VCNs; start/stop VCNs as needed. + + // Re-evaluate subscriptions, and start/stop VCNs. This starts with an empty + // snapshot, and therefore safe even before telephony subscriptions are loaded. + mTelephonySubscriptionTrackerCb.onNewSnapshot(mLastSnapshot); } } }); @@ -203,6 +233,14 @@ public class VcnManagementService extends IVcnManagementService.Stub { return mHandlerThread.getLooper(); } + /** Creates a new VcnInstance using the provided configuration */ + public TelephonySubscriptionTracker newTelephonySubscriptionTracker( + @NonNull Context context, + @NonNull Looper looper, + @NonNull TelephonySubscriptionTrackerCallback callback) { + return new TelephonySubscriptionTracker(context, new Handler(looper), callback); + } + /** * Retrieves the caller's UID * @@ -225,12 +263,29 @@ public class VcnManagementService extends IVcnManagementService.Stub { newPersistableBundleLockingReadWriteHelper(@NonNull String path) { return new PersistableBundleUtils.LockingReadWriteHelper(path); } + + /** Creates a new VcnContext */ + public VcnContext newVcnContext( + @NonNull Context context, + @NonNull Looper looper, + @NonNull VcnNetworkProvider vcnNetworkProvider) { + return new VcnContext(context, looper, vcnNetworkProvider); + } + + /** Creates a new Vcn instance using the provided configuration */ + public Vcn newVcn( + @NonNull VcnContext vcnContext, + @NonNull ParcelUuid subscriptionGroup, + @NonNull VcnConfig config) { + return new Vcn(vcnContext, subscriptionGroup, config); + } } /** Notifies the VcnManagementService that external dependencies can be set up. */ public void systemReady() { mContext.getSystemService(ConnectivityManager.class) .registerNetworkProvider(mNetworkProvider); + mTelephonySubscriptionTracker.register(); } private void enforcePrimaryUser() { @@ -277,27 +332,112 @@ public class VcnManagementService extends IVcnManagementService.Stub { "Carrier privilege required for subscription group to set VCN Config"); } + private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback { + /** + * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker} + * + * <p>Start any unstarted VCN instances + * + * @hide + */ + public void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { + // Startup VCN instances + synchronized (mLock) { + mLastSnapshot = snapshot; + + // Start any VCN instances as necessary + for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) { + if (snapshot.packageHasPermissionsForSubscriptionGroup( + entry.getKey(), entry.getValue().getProvisioningPackageName())) { + if (!mVcns.containsKey(entry.getKey())) { + startVcnLocked(entry.getKey(), entry.getValue()); + } + + // Cancel any scheduled teardowns for active subscriptions + mHandler.removeCallbacksAndMessages(mVcns.get(entry.getKey())); + } + } + + // Schedule teardown of any VCN instances that have lost carrier privileges (after a + // delay) + for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) { + final VcnConfig config = mConfigs.get(entry.getKey()); + if (config == null + || !snapshot.packageHasPermissionsForSubscriptionGroup( + entry.getKey(), config.getProvisioningPackageName())) { + final ParcelUuid uuidToTeardown = entry.getKey(); + final Vcn instanceToTeardown = entry.getValue(); + + mHandler.postDelayed(() -> { + synchronized (mLock) { + // Guard against case where this is run after a old instance was + // torn down, and a new instance was started. Verify to ensure + // correct instance is torn down. This could happen as a result of a + // Carrier App manually removing/adding a VcnConfig. + if (mVcns.get(uuidToTeardown) == instanceToTeardown) { + mVcns.remove(uuidToTeardown).teardownAsynchronously(); + } + } + }, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + } + } + } + } + } + + @GuardedBy("mLock") + private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { + Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup); + + // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active + // VCN. + + final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config); + mVcns.put(subscriptionGroup, newInstance); + } + + @GuardedBy("mLock") + private void startOrUpdateVcnLocked( + @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { + Slog.v(TAG, "Starting or updating VCN config for subGrp: " + subscriptionGroup); + + if (mVcns.containsKey(subscriptionGroup)) { + mVcns.get(subscriptionGroup).updateConfig(config); + } else { + startVcnLocked(subscriptionGroup, config); + } + } + /** * Sets a VCN config for a given subscription group. * * <p>Implements the IVcnManagementService Binder interface. */ @Override - public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { + public void setVcnConfig( + @NonNull ParcelUuid subscriptionGroup, + @NonNull VcnConfig config, + @NonNull String opPkgName) { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); requireNonNull(config, "config was null"); + requireNonNull(opPkgName, "opPkgName was null"); + if (!config.getProvisioningPackageName().equals(opPkgName)) { + throw new IllegalArgumentException("Mismatched caller and VcnConfig creator"); + } + Slog.v(TAG, "VCN config updated for subGrp: " + subscriptionGroup); + mContext.getSystemService(AppOpsManager.class) + .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName()); enforceCallingUserAndCarrierPrivilege(subscriptionGroup); - synchronized (mLock) { - mConfigs.put(subscriptionGroup, config); + Binder.withCleanCallingIdentity(() -> { + synchronized (mLock) { + mConfigs.put(subscriptionGroup, config); + startOrUpdateVcnLocked(subscriptionGroup, config); - // Must be done synchronously to ensure that writes do not happen out-of-order. - writeConfigsToDiskLocked(); - } - - // TODO: Clear Binder calling identity - // TODO: Trigger startup as necessary + writeConfigsToDiskLocked(); + } + }); } /** @@ -308,18 +448,21 @@ public class VcnManagementService extends IVcnManagementService.Stub { @Override public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); + Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup); enforceCallingUserAndCarrierPrivilege(subscriptionGroup); - synchronized (mLock) { - mConfigs.remove(subscriptionGroup); + Binder.withCleanCallingIdentity(() -> { + synchronized (mLock) { + mConfigs.remove(subscriptionGroup); - // Must be done synchronously to ensure that writes do not happen out-of-order. - writeConfigsToDiskLocked(); - } + if (mVcns.containsKey(subscriptionGroup)) { + mVcns.remove(subscriptionGroup).teardownAsynchronously(); + } - // TODO: Clear Binder calling identity - // TODO: Trigger teardown as necessary + writeConfigsToDiskLocked(); + } + }); } @GuardedBy("mLock") @@ -345,19 +488,11 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } - /** - * Network provider for VCN networks. - * - * @hide - */ - public class VcnNetworkProvider extends NetworkProvider { - VcnNetworkProvider(Context context, Looper looper) { - super(context, looper, VcnNetworkProvider.class.getSimpleName()); - } - - @Override - public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { - // TODO: Handle network requests - Ensure VCN started, and start appropriate tunnels. + /** Get current configuration list for testing purposes */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public Map<ParcelUuid, Vcn> getAllVcns() { + synchronized (mLock) { + return Collections.unmodifiableMap(mVcns); } } } diff --git a/services/core/java/com/android/server/utils/WatchableIntentResolver.java b/services/core/java/com/android/server/WatchableIntentResolver.java index 767fc07d8cad..2ef94f17e5d9 100644 --- a/services/core/java/com/android/server/utils/WatchableIntentResolver.java +++ b/services/core/java/com/android/server/WatchableIntentResolver.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package com.android.server.utils; +package com.android.server; import android.annotation.NonNull; import android.annotation.Nullable; -import com.android.server.IntentResolver; +import com.android.server.utils.Watchable; +import com.android.server.utils.WatchableImpl; +import com.android.server.utils.Watcher; import java.util.List; @@ -37,28 +39,45 @@ public abstract class WatchableIntentResolver<F, R extends Object> * Watchable machinery */ private final Watchable mWatchable = new WatchableImpl(); + /** * Register an observer to receive change notifications. * @param observer The observer to register. */ + @Override public void registerObserver(@NonNull Watcher observer) { mWatchable.registerObserver(observer); } + /** * Unregister the observer, which will no longer receive change notifications. * @param observer The observer to unregister. */ + @Override public void unregisterObserver(@NonNull Watcher observer) { mWatchable.unregisterObserver(observer); } + + /** + * Return true if the {@link Watcher) is a registered observer. + * @param observer A {@link Watcher} that might be registered + * @return true if the observer is registered with this {@link Watchable}. + */ + @Override + public boolean isRegisteredObserver(@NonNull Watcher observer) { + return mWatchable.isRegisteredObserver(observer); + } + /** * Notify listeners that the object has changd. The argument is a hint as to the * source of the change. * @param what The attribute or sub-object that changed, if not null. */ + @Override public void dispatchChange(@Nullable Watchable what) { mWatchable.dispatchChange(what); } + /** * Notify listeners that this object has changed. */ diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 15e31ba0f140..117098315ef7 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4756,7 +4756,7 @@ public class AccountManagerService IAccountManagerResponse getResponseAndClose() { if (mResponse == null) { - // this session has already been closed + close(); return null; } IAccountManagerResponse response = mResponse; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 097afd51ef9b..7af328aed8b4 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -61,7 +61,6 @@ import android.app.admin.DevicePolicyEventLogger; import android.app.compat.CompatChanges; import android.appwidget.AppWidgetManagerInternal; import android.compat.annotation.ChangeId; -import android.compat.annotation.Disabled; import android.compat.annotation.EnabledSince; import android.content.ComponentName; import android.content.ComponentName.WithComponentName; @@ -108,6 +107,7 @@ import android.webkit.WebViewZygote; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.procstats.ServiceState; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; @@ -170,6 +170,7 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 {} @@ -275,7 +277,7 @@ public final class ActiveServices { * is higher than R. */ @ChangeId - @Disabled + @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S) static final long FGS_BG_START_RESTRICTION_CHANGE_ID = 170668199L; /** @@ -307,9 +309,40 @@ public final class ActiveServices { */ private static final ArraySet<String> sFgsBgStartExemptedPackages = new ArraySet<>(); + private static final ArrayList<String> sFgsBgStartExemptedPackagePrefixes = new ArrayList<>(); + + /** + * List of packages that are exempted from the FGS restriction *for now*. + * + * STOPSHIP(/b/176844961) Remove it. Also update ActiveServicesTest.java. + */ + private static final String[] FGS_BG_START_EXEMPTED_PACKAGES = { + "com.google.pixel.exo.bootstrapping", + }; + + /** + * List of packages that are exempted from the FGS restriction *for now*. We also allow + * any packages that + * + * STOPSHIP(/b/176844961) Remove it. Also update ActiveServicesTest.java. + */ + private static final String[] FGS_BG_START_EXEMPTED_PACKAGES_PREFIXED_ALLOWED = { + "com.android.webview", + "com.google.android.webview", + "com.android.chrome", + "com.google.android.apps.chrome", + "com.chrome", + }; + static { - sFgsBgStartExemptedPackages.add("com.google.pixel.exo.bootstrapping"); //STOPSHIP Remove it. - sFgsBgStartExemptedPackages.add("com.android.chrome"); // STOPSHIP Remove it. + for (String s : FGS_BG_START_EXEMPTED_PACKAGES) { + sFgsBgStartExemptedPackages.add(s); + } + + for (String s : FGS_BG_START_EXEMPTED_PACKAGES_PREFIXED_ALLOWED) { + sFgsBgStartExemptedPackages.add(s); // Add it for an exact match. + sFgsBgStartExemptedPackagePrefixes.add(s + "."); // Add it for an prefix match. + } } final Runnable mLastAnrDumpClearer = new Runnable() { @@ -5348,6 +5381,14 @@ public final class ActiveServices { } } + 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 @@ -5365,10 +5406,25 @@ public final class ActiveServices { return ret; } - private boolean isPackageExemptedFromFgsRestriction(String packageName, int uid) { - if (!sFgsBgStartExemptedPackages.contains(packageName)) { - return false; + @VisibleForTesting + static boolean isPackageExemptedFromFgsRestriction(String packageName, int uid) { + boolean exempted = false; + if (sFgsBgStartExemptedPackages.contains(packageName)) { + exempted = true; + } else { + for (String pkg : sFgsBgStartExemptedPackagePrefixes) { + if (packageName.startsWith(pkg)) { + exempted = true; + break; + } + } } + if (!exempted) { + return false; // Package isn't exempted. + } + // Allow exempted packages to be subject to the restriction using this compat ID. + // (so that, for example, the webview developer will be able to test the restriction + // locally.) return CompatChanges.isChangeEnabled(FGS_BG_START_USE_EXEMPTION_LIST_CHANGE_ID, uid); } @@ -5416,6 +5472,8 @@ public final class ActiveServices { 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 ""; } @@ -5451,9 +5509,7 @@ public final class ActiveServices { } private boolean isBgFgsRestrictionEnabled(ServiceRecord r) { - if (mAm.mConstants.mFlagFgsStartRestrictionEnabled) { - return true; - } - return CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid); + return mAm.mConstants.mFlagFgsStartRestrictionEnabled + && CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 6892ef770901..94643f10dd63 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -360,10 +360,11 @@ final class ActivityManagerConstants extends ContentObserver { // started, the restriction is on while-in-use permissions.) volatile boolean mFlagBackgroundFgsStartRestrictionEnabled = true; - // Indicates whether the foreground service background start restriction is enabled. + // Indicates whether the foreground service background start restriction is enabled for + // apps targeting S+. // When the restriction is enabled, service is not allowed to startForeground from background // at all. - volatile boolean mFlagFgsStartRestrictionEnabled = false; + volatile boolean mFlagFgsStartRestrictionEnabled = true; // Whether we defer FGS notifications a few seconds following their transition to // the foreground state. Applies only to S+ apps; enabled by default. @@ -792,7 +793,7 @@ final class ActivityManagerConstants extends ContentObserver { mFlagFgsStartRestrictionEnabled = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED, - /*defaultValue*/ false); + /*defaultValue*/ true); } private void updateFgsNotificationDeferralEnable() { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a094eac73471..21b6b557169c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -573,6 +573,9 @@ public class ActivityManagerService extends IActivityManager.Stub 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; @@ -5730,20 +5733,6 @@ public class ActivityManagerService extends IActivityManager.Stub return mActivityTaskManager.getRecentTasks(maxNum, flags, userId); } - /** - * Moves the top activity in the input rootTaskId to the pinned root task. - * - * @param rootTaskId Id of root task to move the top activity to pinned root task. - * @param bounds Bounds to use for pinned root task. - * - * @return True if the top activity of the input root task was successfully moved to the pinned - * root task. - */ - @Override - public boolean moveTopActivityToPinnedRootTask(int rootTaskId, Rect bounds) { - return mActivityTaskManager.moveTopActivityToPinnedRootTask(rootTaskId, bounds); - } - @Override public List<RootTaskInfo> getAllRootTaskInfos() { return mActivityTaskManager.getAllRootTaskInfos(); @@ -16798,6 +16787,22 @@ public class ActivityManagerService extends IActivityManager.Stub } @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/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index fffa814566fb..fe71fbf79157 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -2596,8 +2596,6 @@ final class ActivityManagerShellCommand extends ShellCommand { return runStackList(pw); case "info": return runRootTaskInfo(pw); - case "move-top-activity-to-pinned-stack": - return runMoveTopActivityToPinnedRootTask(pw); case "remove": return runRootTaskRemove(pw); default: @@ -2687,41 +2685,6 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } - int runMoveTopActivityToPinnedRootTask(PrintWriter pw) throws RemoteException { - int rootTaskId = Integer.parseInt(getNextArgRequired()); - final Rect bounds = getBounds(); - if (bounds == null) { - getErrPrintWriter().println("Error: invalid input bounds"); - return -1; - } - - if (!mTaskInterface.moveTopActivityToPinnedRootTask(rootTaskId, bounds)) { - getErrPrintWriter().println("Didn't move top activity to pinned stack."); - return -1; - } - return 0; - } - - void setBoundsSide(Rect bounds, String side, int value) { - switch (side) { - case "l": - bounds.left = value; - break; - case "r": - bounds.right = value; - break; - case "t": - bounds.top = value; - break; - case "b": - bounds.bottom = value; - break; - default: - getErrPrintWriter().println("Unknown set side: " + side); - break; - } - } - int runTask(PrintWriter pw) throws RemoteException { String op = getNextArgRequired(); if (op.equals("lock")) { @@ -3386,16 +3349,6 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" move-task <TASK_ID> <STACK_ID> [true|false]"); pw.println(" Move <TASK_ID> from its current stack to the top (true) or"); pw.println(" bottom (false) of <STACK_ID>."); - pw.println(" resize-docked-stack <LEFT,TOP,RIGHT,BOTTOM> [<TASK_LEFT,TASK_TOP,TASK_RIGHT,TASK_BOTTOM>]"); - pw.println(" Change docked stack to <LEFT,TOP,RIGHT,BOTTOM>"); - pw.println(" and supplying temporary different task bounds indicated by"); - pw.println(" <TASK_LEFT,TOP,RIGHT,BOTTOM>"); - pw.println(" move-top-activity-to-pinned-stack: <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>"); - pw.println(" Moves the top activity from"); - pw.println(" <STACK_ID> to the pinned stack using <LEFT,TOP,RIGHT,BOTTOM> for the"); - pw.println(" bounds of the pinned stack."); - pw.println(" positiontask <TASK_ID> <STACK_ID> <POSITION>"); - pw.println(" Place <TASK_ID> in <STACK_ID> at <POSITION>"); pw.println(" list"); pw.println(" List all of the activity stacks and their sizes."); pw.println(" info <WINDOWING_MODE> <ACTIVITY_TYPE>"); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index e90423c2566a..90abc0cb6e03 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -2069,6 +2069,13 @@ class ProcessRecord implements WindowProcessListener { } 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/AppHibernationConstants.java b/services/core/java/com/android/server/apphibernation/AppHibernationConstants.java new file mode 100644 index 000000000000..9d43a39072bf --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/AppHibernationConstants.java @@ -0,0 +1,28 @@ +/* + * 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.apphibernation; + +/** + * Flags and constants that modify app hibernation behavior. + */ +final class AppHibernationConstants { + + private AppHibernationConstants() {} + + // Device config feature flag for app hibernation + static final String KEY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled"; +} diff --git a/services/core/java/com/android/server/apphibernation/OWNERS b/services/core/java/com/android/server/apphibernation/OWNERS new file mode 100644 index 000000000000..c2e27e084c8c --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/OWNERS @@ -0,0 +1 @@ +include /core/java/android/apphibernation/OWNERS diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 0943c9ca17fe..5663495db037 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityTaskManager; -import android.app.IActivityTaskManager; import android.app.TaskStackListener; import android.content.ComponentName; import android.content.Context; diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java b/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java index d588b8d4aa13..49cddaaf5ef8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java @@ -21,8 +21,10 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.os.AsyncTask; import android.os.Environment; +import android.util.AtomicFile; import android.util.Slog; import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; import android.util.Xml; import com.android.internal.annotations.GuardedBy; @@ -35,6 +37,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -46,17 +49,16 @@ import java.util.List; public abstract class BiometricUserState<T extends BiometricAuthenticator.Identifier> { private static final String TAG = "UserState"; + private static final String TAG_INVALIDATION = "authenticatorIdInvalidation_tag"; + private static final String ATTR_INVALIDATION = "authenticatorIdInvalidation_attr"; + @GuardedBy("this") protected final ArrayList<T> mBiometrics = new ArrayList<>(); + protected boolean mInvalidationInProgress; protected final Context mContext; protected final File mFile; - private final Runnable mWriteStateRunnable = new Runnable() { - @Override - public void run() { - doWriteState(); - } - }; + private final Runnable mWriteStateRunnable = this::doWriteStateInternal; /** * @return The tag for the biometrics. There may be multiple instances of a biometric within. @@ -73,10 +75,40 @@ public abstract class BiometricUserState<T extends BiometricAuthenticator.Identi */ protected abstract ArrayList<T> getCopy(ArrayList<T> array); + protected abstract void doWriteState(@NonNull TypedXmlSerializer serializer) throws Exception; + /** - * @return Writes the cached data to persistent storage. + * @Writes the cached data to persistent storage. */ - protected abstract void doWriteState(); + private void doWriteStateInternal() { + AtomicFile destination = new AtomicFile(mFile); + + FileOutputStream out = null; + + try { + out = destination.startWrite(); + TypedXmlSerializer serializer = Xml.resolveSerializer(out); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startDocument(null, true); + + // Store the authenticatorId + serializer.startTag(null, TAG_INVALIDATION); + serializer.attributeBoolean(null, ATTR_INVALIDATION, mInvalidationInProgress); + serializer.endTag(null, TAG_INVALIDATION); + + // Do any additional serialization that subclasses may require + doWriteState(serializer); + + serializer.endDocument(); + destination.finishWrite(out); + } catch (Throwable t) { + Slog.wtf(TAG, "Failed to write settings, restoring backup", t); + destination.failWrite(out); + throw new IllegalStateException("Failed to write to file: " + mFile.toString(), t); + } finally { + IoUtils.closeQuietly(out); + } + } /** * @return @@ -93,6 +125,19 @@ public abstract class BiometricUserState<T extends BiometricAuthenticator.Identi } } + public void setInvalidationInProgress(boolean invalidationInProgress) { + synchronized (this) { + mInvalidationInProgress = invalidationInProgress; + scheduleWriteStateLocked(); + } + } + + public boolean isInvalidationInProgress() { + synchronized (this) { + return mInvalidationInProgress; + } + } + public void addBiometric(T identifier) { synchronized (this) { mBiometrics.add(identifier); @@ -202,6 +247,8 @@ public abstract class BiometricUserState<T extends BiometricAuthenticator.Identi String tagName = parser.getName(); if (tagName.equals(getBiometricsTag())) { parseBiometricsLocked(parser); + } else if (tagName.equals(TAG_INVALIDATION)) { + mInvalidationInProgress = parser.getAttributeBoolean(null, ATTR_INVALIDATION); } } } diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java index d52b3405c6f0..ebe467942790 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java @@ -31,4 +31,6 @@ public interface BiometricUtils<T extends BiometricAuthenticator.Identifier> { void removeBiometricForUser(Context context, int userId, int biometricId); void renameBiometricForUser(Context context, int userId, int biometricId, CharSequence name); CharSequence getUniqueName(Context context, int userId); + void setInvalidationInProgress(Context context, int userId, boolean inProgress); + boolean isInvalidationInProgress(Context context, int userId); }
\ No newline at end of file diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java new file mode 100644 index 000000000000..fe946cb0189d --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java @@ -0,0 +1,63 @@ +/* + * 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.sensors; + +import android.annotation.NonNull; +import android.content.Context; +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. + */ +public abstract class InvalidationClient<S extends BiometricAuthenticator.Identifier, T> + 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, + @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) { + mAuthenticatorIds.put(getTargetUserId(), newAuthenticatorId); + mCallback.onClientFinished(this, true /* success */); + } + + @Override + public void start(@NonNull Callback callback) { + super.start(callback); + + startHalOperation(); + } + + @Override + public void unableToStart() { + + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java new file mode 100644 index 000000000000..ca34eee17f7f --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java @@ -0,0 +1,86 @@ +/* + * 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.sensors; + +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.BiometricsProtoEnums; + +/** + * ClientMonitor subclass responsible for coordination of authenticatorId invalidation of other + * sensors. See {@link InvalidationClient} for the ClientMonitor subclass responsible for initiating + * the invalidation with individual HALs. AuthenticatorId invalidation is required on devices with + * multiple strong biometric sensors. + * + * The public Keystore and Biometric APIs are biometric-tied, not modality-tied, meaning that keys + * are unlockable by "any/all strong biometrics on the device", and not "only a specific strong + * sensor". The Keystore API allows for creation of biometric-tied keys that are invalidated upon + * new biometric enrollment. See + * {@link android.security.keystore.KeyGenParameterSpec.Builder#setInvalidatedByBiometricEnrollment} + * + * This has been supported on single-sensor devices by the various getAuthenticatorId APIs on the + * HIDL and AIDL biometric HAL interfaces, where: + * 1) authenticatorId is requested and stored during key generation + * 2) authenticatorId is contained within the HAT when biometric authentication succeeds + * 3) authenticatorId is automatically changed (below the framework) whenever a new biometric + * enrollment occurs. + * + * For multi-biometric devices, this will be done the following way: + * 1) New enrollment added for Sensor1. Sensor1's HAL/TEE updates its authenticatorId automatically + * when enrollment completes + * 2) Framework marks Sensor1 as "invalidationInProgress". See + * {@link BiometricUtils#setInvalidationInProgress(Context, int, boolean)} + * 3) After all other sensors have finished invalidation, the framework will clear the invalidation + * flag for Sensor1. + * 4) New keys that are generated will include all new authenticatorIds + * + * The above is robust to incomplete invalidation. For example, when system boots or after user + * 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> { + + private final BiometricManager mBiometricManager; + + public InvalidationRequesterClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon, + int userId, int sensorId) { + 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); + } + + @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) + } + + @Override + public void unableToStart() { + + } + + @Override + protected void startHalOperation() { + // No HAL operations necessary + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java index d85ab25cc812..3ca069100fbe 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java @@ -38,6 +38,7 @@ public abstract class LoggableMonitor { private final int mStatsAction; private final int mStatsClient; private long mFirstAcquireTimeMs; + private boolean mShouldLogMetrics = true; /** * Only valid for AuthenticationClient. @@ -58,6 +59,10 @@ public abstract class LoggableMonitor { mStatsClient = statsClient; } + protected void setShouldLog(boolean shouldLog) { + mShouldLogMetrics = shouldLog; + } + public int getStatsClient() { return mStatsClient; } @@ -70,6 +75,9 @@ public abstract class LoggableMonitor { protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode, int targetUserId) { + if (!mShouldLogMetrics) { + return; + } final boolean isFace = mStatsModality == BiometricsProtoEnums.MODALITY_FACE; final boolean isFingerprint = mStatsModality == BiometricsProtoEnums.MODALITY_FINGERPRINT; @@ -110,6 +118,10 @@ public abstract class LoggableMonitor { protected final void logOnError(Context context, int error, int vendorCode, int targetUserId) { + if (!mShouldLogMetrics) { + return; + } + final long latency = mFirstAcquireTimeMs != 0 ? (System.currentTimeMillis() - mFirstAcquireTimeMs) : -1; @@ -144,6 +156,10 @@ public abstract class LoggableMonitor { protected final void logOnAuthenticated(Context context, boolean authenticated, boolean requireConfirmation, int targetUserId, boolean isBiometricPrompt) { + if (!mShouldLogMetrics) { + return; + } + int authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__UNKNOWN; if (!authenticated) { authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__REJECTED; @@ -189,6 +205,10 @@ public abstract class LoggableMonitor { } protected final void logOnEnrolled(int targetUserId, long latency, boolean enrollSuccessful) { + if (!mShouldLogMetrics) { + return; + } + if (DEBUG) { Slog.v(TAG, "Enrolled! Modality: " + mStatsModality + ", User: " + targetUserId diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUserState.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUserState.java index a26662dfd970..a9981d0cfc20 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceUserState.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUserState.java @@ -16,23 +16,18 @@ package com.android.server.biometrics.sensors.face; +import android.annotation.NonNull; import android.content.Context; import android.hardware.face.Face; -import android.util.AtomicFile; -import android.util.Slog; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; -import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.server.biometrics.sensors.BiometricUserState; -import libcore.io.IoUtils; - import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; @@ -75,46 +70,26 @@ public class FaceUserState extends BiometricUserState<Face> { } @Override - protected void doWriteState() { - AtomicFile destination = new AtomicFile(mFile); - - ArrayList<Face> faces; + protected void doWriteState(@NonNull TypedXmlSerializer serializer) throws Exception { + final ArrayList<Face> faces; synchronized (this) { faces = getCopy(mBiometrics); } - FileOutputStream out = null; - try { - out = destination.startWrite(); - - TypedXmlSerializer serializer = Xml.resolveSerializer(out); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - serializer.startDocument(null, true); - serializer.startTag(null, TAG_FACES); - - final int count = faces.size(); - for (int i = 0; i < count; i++) { - Face f = faces.get(i); - serializer.startTag(null, TAG_FACE); - serializer.attributeInt(null, ATTR_FACE_ID, f.getBiometricId()); - serializer.attribute(null, ATTR_NAME, f.getName().toString()); - serializer.attributeLong(null, ATTR_DEVICE_ID, f.getDeviceId()); - serializer.endTag(null, TAG_FACE); - } + serializer.startTag(null, TAG_FACES); - serializer.endTag(null, TAG_FACES); - serializer.endDocument(); - destination.finishWrite(out); - - // Any error while writing is fatal. - } catch (Throwable t) { - Slog.wtf(TAG, "Failed to write settings, restoring backup", t); - destination.failWrite(out); - throw new IllegalStateException("Failed to write faces", t); - } finally { - IoUtils.closeQuietly(out); + final int count = faces.size(); + for (int i = 0; i < count; i++) { + Face f = faces.get(i); + serializer.startTag(null, TAG_FACE); + serializer.attributeInt(null, ATTR_FACE_ID, f.getBiometricId()); + serializer.attribute(null, ATTR_NAME, f.getName().toString()); + serializer.attributeLong(null, ATTR_DEVICE_ID, f.getDeviceId()); + serializer.endTag(null, TAG_FACE); } + + serializer.endTag(null, TAG_FACES); } @GuardedBy("this") diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java index a0cd4a56ea12..c5744780cd71 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java @@ -18,7 +18,6 @@ package com.android.server.biometrics.sensors.face; import android.annotation.Nullable; import android.content.Context; -import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.face.Face; import android.text.TextUtils; import android.util.SparseArray; @@ -115,6 +114,16 @@ public class FaceUtils implements BiometricUtils<Face> { return getStateForUser(context, userId).getUniqueName(); } + @Override + public void setInvalidationInProgress(Context context, int userId, boolean inProgress) { + getStateForUser(context, userId).setInvalidationInProgress(inProgress); + } + + @Override + public boolean isInvalidationInProgress(Context context, int userId) { + return getStateForUser(context, userId).isInvalidationInProgress(); + } + private FaceUserState getStateForUser(Context ctx, int userId) { synchronized (this) { FaceUserState state = mUserStates.get(userId); 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 new file mode 100644 index 000000000000..9c6438ecd014 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java @@ -0,0 +1,49 @@ +/* + * 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.sensors.face.aidl; + +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.hardware.face.Face; +import android.os.RemoteException; +import android.util.Slog; + +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, @NonNull Map<Integer, Long> authenticatorIds) { + super(context, lazyDaemon, userId, sensorId, utils, authenticatorIds); + } + + @Override + protected void startHalOperation() { + try { + getFreshDaemon().invalidateAuthenticatorId(mSequentialId); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + mCallback.onClientFinished(this, false /* success */); + } + } +} 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 61f9cc40d233..e6cdbd28f9d1 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 @@ -66,10 +66,10 @@ import com.android.internal.widget.LockPatternUtils; 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.fingerprint.aidl.FingerprintProvider; import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21; import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock; @@ -188,7 +188,8 @@ public class FingerprintService extends SystemService implements BiometricServic @Override // Binder call public void enroll(final IBinder token, final byte[] hardwareAuthToken, final int userId, - final IFingerprintServiceReceiver receiver, final String opPackageName) { + final IFingerprintServiceReceiver receiver, final String opPackageName, + boolean shouldLogMetrics) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); final Pair<Integer, ServiceProvider> provider = getSingleProvider(); @@ -198,7 +199,7 @@ public class FingerprintService extends SystemService implements BiometricServic } provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, - receiver, opPackageName); + receiver, opPackageName, shouldLogMetrics); } @Override // Binder call diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUserState.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUserState.java index 671e08bd8c56..ae173f7bf7e3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUserState.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUserState.java @@ -16,23 +16,18 @@ package com.android.server.biometrics.sensors.fingerprint; +import android.annotation.NonNull; import android.content.Context; import android.hardware.fingerprint.Fingerprint; -import android.util.AtomicFile; -import android.util.Slog; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; -import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.server.biometrics.sensors.BiometricUserState; -import libcore.io.IoUtils; - import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; @@ -76,47 +71,27 @@ public class FingerprintUserState extends BiometricUserState<Fingerprint> { } @Override - protected void doWriteState() { - AtomicFile destination = new AtomicFile(mFile); - - ArrayList<Fingerprint> fingerprints; + protected void doWriteState(@NonNull TypedXmlSerializer serializer) throws Exception { + final ArrayList<Fingerprint> fingerprints; synchronized (this) { fingerprints = getCopy(mBiometrics); } - FileOutputStream out = null; - try { - out = destination.startWrite(); - - TypedXmlSerializer serializer = Xml.resolveSerializer(out); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - serializer.startDocument(null, true); - serializer.startTag(null, TAG_FINGERPRINTS); - - final int count = fingerprints.size(); - for (int i = 0; i < count; i++) { - Fingerprint fp = fingerprints.get(i); - serializer.startTag(null, TAG_FINGERPRINT); - serializer.attributeInt(null, ATTR_FINGER_ID, fp.getBiometricId()); - serializer.attribute(null, ATTR_NAME, fp.getName().toString()); - serializer.attributeInt(null, ATTR_GROUP_ID, fp.getGroupId()); - serializer.attributeLong(null, ATTR_DEVICE_ID, fp.getDeviceId()); - serializer.endTag(null, TAG_FINGERPRINT); - } - - serializer.endTag(null, TAG_FINGERPRINTS); - serializer.endDocument(); - destination.finishWrite(out); - - // Any error while writing is fatal. - } catch (Throwable t) { - Slog.wtf(TAG, "Failed to write settings, restoring backup", t); - destination.failWrite(out); - throw new IllegalStateException("Failed to write fingerprints", t); - } finally { - IoUtils.closeQuietly(out); + serializer.startTag(null, TAG_FINGERPRINTS); + + final int count = fingerprints.size(); + for (int i = 0; i < count; i++) { + Fingerprint fp = fingerprints.get(i); + serializer.startTag(null, TAG_FINGERPRINT); + serializer.attributeInt(null, ATTR_FINGER_ID, fp.getBiometricId()); + serializer.attribute(null, ATTR_NAME, fp.getName().toString()); + serializer.attributeInt(null, ATTR_GROUP_ID, fp.getGroupId()); + serializer.attributeLong(null, ATTR_DEVICE_ID, fp.getDeviceId()); + serializer.endTag(null, TAG_FINGERPRINT); } + + serializer.endTag(null, TAG_FINGERPRINTS); } @GuardedBy("this") diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java index b3d2419901e4..dc6fd3a1b26d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java @@ -118,6 +118,16 @@ public class FingerprintUtils implements BiometricUtils<Fingerprint> { return getStateForUser(context, userId).getUniqueName(); } + @Override + public void setInvalidationInProgress(Context context, int userId, boolean inProgress) { + getStateForUser(context, userId).setInvalidationInProgress(inProgress); + } + + @Override + public boolean isInvalidationInProgress(Context context, int userId) { + return getStateForUser(context, userId).isInvalidationInProgress(); + } + private FingerprintUserState getStateForUser(Context ctx, int userId) { synchronized (this) { FingerprintUserState state = mUserStates.get(userId); 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 6e36cd24b2e7..d94c98481eeb 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 @@ -76,7 +76,8 @@ public interface ServiceProvider { @NonNull String opPackageName, long challenge); void scheduleEnroll(int sensorId, @NonNull IBinder token, byte[] hardwareAuthToken, int userId, - @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName); + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, + boolean shouldLogMetrics); void cancelEnrollment(int sensorId, @NonNull IBinder token); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java index 3f9aef2b8651..e95447b49872 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java @@ -121,7 +121,7 @@ class BiometricTestSessionImpl extends ITestSession.Stub { Utils.checkPermission(mContext, TEST_BIOMETRIC); mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver, - mContext.getOpPackageName()); + mContext.getOpPackageName(), true /* shouldLogMetrics */); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 3e13c45d335e..ab59abd25b7d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -51,12 +51,14 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, - @Nullable IUdfpsOverlayController udfpsOvelayController, int maxTemplatesPerUser) { + @Nullable IUdfpsOverlayController udfpsOvelayController, int maxTemplatesPerUser, + boolean shouldLogMetrics) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, true /* shouldVibrate */); mUdfpsOverlayController = udfpsOvelayController; mMaxTemplatesPerUser = maxTemplatesPerUser; + setShouldLog(shouldLogMetrics); } @Override 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 new file mode 100644 index 000000000000..3d07334f04a1 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java @@ -0,0 +1,49 @@ +/* + * 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.sensors.fingerprint.aidl; + +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.Fingerprint; +import android.os.RemoteException; +import android.util.Slog; + +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, @NonNull Map<Integer, Long> authenticatorIds) { + super(context, lazyDaemon, userId, sensorId, utils, authenticatorIds); + } + + @Override + protected void startHalOperation() { + try { + getFreshDaemon().invalidateAuthenticatorId(mSequentialId); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + mCallback.onClientFinished(this, false /* success */); + } + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 99c662a57c3b..a03debadf36c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityTaskManager; -import android.app.IActivityTaskManager; import android.app.TaskStackListener; import android.content.Context; import android.content.pm.UserInfo; @@ -348,7 +347,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @Override public void scheduleEnroll(int sensorId, @NonNull IBinder token, byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, - @NonNull String opPackageName) { + @NonNull String opPackageName, boolean shouldLogMetrics) { mHandler.post(() -> { final IFingerprint daemon = getHalInstance(); if (daemon == null) { @@ -370,7 +369,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mSensors.get(sensorId).getLazySession(), token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, - mUdfpsOverlayController, maxTemplatesPerUser); + mUdfpsOverlayController, maxTemplatesPerUser, shouldLogMetrics); scheduleForSensor(sensorId, client, new ClientMonitor.Callback() { @Override public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java index 74549b917e82..95c4cee7e59e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java @@ -122,7 +122,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { Utils.checkPermission(mContext, TEST_BIOMETRIC); mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver, - mContext.getOpPackageName()); + mContext.getOpPackageName(), true/* shouldLogMetrics */); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index b8d27aa61806..f5ce8943c188 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityTaskManager; -import android.app.IActivityTaskManager; import android.app.SynchronousUserSwitchObserver; import android.app.TaskStackListener; import android.app.UserSwitchObserver; @@ -548,14 +547,16 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @Override public void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, - @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, + boolean shouldLogMetrics) { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), - ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController); + ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController, + shouldLogMetrics); mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() { @Override public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index af61a8be3052..d1f1cf832935 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -52,11 +52,13 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId, - @Nullable IUdfpsOverlayController udfpsOverlayController) { + @Nullable IUdfpsOverlayController udfpsOverlayController, + boolean shouldLogMetrics) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, true /* shouldVibrate */); mUdfpsOverlayController = udfpsOverlayController; + setShouldLog(shouldLogMetrics); } @Override diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index c4ff99bae694..9ba957ef27ae 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 @@ public final class CompatChange extends CompatibilityChangeInfo { 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 @@ public final class CompatChange extends CompatibilityChangeInfo { * @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 final class CompatChange extends CompatibilityChangeInfo { 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) { @@ -213,6 +214,19 @@ public final class CompatChange extends CompatibilityChangeInfo { } /** + * Find if this change will be enabled for the given package after installation. + * + * @param packageName The package name in question + * @return {@code true} if the change should be enabled for the package. + */ + boolean willBeEnabled(String packageName) { + if (hasDeferredOverride(packageName)) { + return mDeferredOverrides.get(packageName); + } + return defaultValue(); + } + + /** * Returns the default value for the change id, assuming there are no overrides. * * @return {@code false} if it's a default disabled change, {@code true} otherwise. @@ -261,6 +275,9 @@ public final class CompatChange extends CompatibilityChangeInfo { 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/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index f03a608232a2..9376e8dc16ea 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -146,6 +146,25 @@ final class CompatConfig { } /** + * Find if a given change will be enabled for a given package name, prior to installation. + * + * @param changeId The ID of the change in question + * @param packageName Package name to check for + * @return {@code true} if the change would be enabled for this package name. Also returns + * {@code true} if the change ID is not known, as unknown changes are enabled by default. + */ + boolean willChangeBeEnabled(long changeId, String packageName) { + synchronized (mChanges) { + CompatChange c = mChanges.get(changeId); + if (c == null) { + // we know nothing about this change: default behaviour is enabled. + return true; + } + return c.willBeEnabled(packageName); + } + } + + /** * Overrides the enabled state for a given change and app. This method is intended to be used * *only* for debugging purposes, ultimately invoked either by an adb command, or from some * developer settings UI. diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 283dba7aff8a..1ea468c341d2 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -137,6 +137,9 @@ public class PlatformCompat extends IPlatformCompat.Stub { @UserIdInt int userId) { checkCompatChangeReadAndLogPermission(); ApplicationInfo appInfo = getApplicationInfo(packageName, userId); + if (appInfo == null) { + return mCompatConfig.willChangeBeEnabled(changeId, packageName); + } return isChangeEnabled(changeId, appInfo); } diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java index 2c06d8230f13..b5d875d5c162 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 @@ final public class IpConnectivityMetrics extends SystemService { 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 7bde4d5a2770..55d8279a92d0 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 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // 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 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { updateRequestCounts(REMOVE, existing); mNetworkRequests.remove(requestId); if (existing.isRequest()) { - unlingerRequest(existing); + unlingerRequest(existing.requestId); } } @@ -839,33 +839,33 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } /** - * 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 06721aea5540..93930ae16de8 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 @@ public class PacManager { * @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 07a4b89be4e9..b250f164a264 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 @@ public class Vpn { } } - /** - * @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/content/OWNERS b/services/core/java/com/android/server/content/OWNERS new file mode 100644 index 000000000000..b6a9fe869ffa --- /dev/null +++ b/services/core/java/com/android/server/content/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/am/OWNERS
\ No newline at end of file diff --git a/services/core/java/com/android/server/content/SyncManager.md b/services/core/java/com/android/server/content/SyncManager.md index 8507abdcc40e..8564fea650ff 100644 --- a/services/core/java/com/android/server/content/SyncManager.md +++ b/services/core/java/com/android/server/content/SyncManager.md @@ -29,8 +29,10 @@ exempted from app-standby throttling. ### Two Levels of Exemption Specifically, there are two different levels of exemption, depending on the state of the caller: -1. `ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET` +1. `ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET`. + This is shown as `STANDBY-EXEMPTED` in `dumpsys content`. 2. `ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET_WITH_TEMP`, which is more powerful than 1. + This is shown as `STANDBY-EXEMPTED(TOP)` in `dumpsys content`. The exemption level is calculated in [ContentService.getSyncExemptionAndCleanUpExtrasForCaller()](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/content/ContentService.java?q=%22int%20getSyncExemptionAndCleanUpExtrasForCaller%22&ss=android%2Fplatform%2Fsuperproject), diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index eb61a1c2ad40..fa063b223250 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -46,6 +46,7 @@ import com.android.internal.BrightnessSynchronizer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.server.EventLogTags; +import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; import java.io.PrintWriter; @@ -195,6 +196,9 @@ class AutomaticBrightnessController { private boolean mShortTermModelValid; private float mShortTermModelAnchor; + // Controls High Brightness Mode. + private HighBrightnessModeController mHbmController; + // Context-sensitive brightness configurations require keeping track of the foreground app's // package name and category, which is done by registering a TaskStackListener to call back to // us onTaskStackChanged, and then using the ActivityTaskManager to get the foreground app's @@ -216,12 +220,12 @@ class AutomaticBrightnessController { float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, - HysteresisLevels screenBrightnessThresholds, Context context) { + HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context) { this(new Injector(), callbacks, looper, sensorManager, lightSensor, mapper, lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig, - ambientBrightnessThresholds, screenBrightnessThresholds, context + ambientBrightnessThresholds, screenBrightnessThresholds, display, context ); } @@ -232,7 +236,7 @@ class AutomaticBrightnessController { float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, - HysteresisLevels screenBrightnessThresholds, Context context) { + HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context) { mInjector = injector; mContext = context; mCallbacks = callbacks; @@ -269,6 +273,20 @@ class AutomaticBrightnessController { mPendingForegroundAppPackageName = null; mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; + + final DisplayDeviceConfig ddConfig = + display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig(); + HighBrightnessModeData hbmData = + ddConfig != null ? ddConfig.getHighBrightnessModeData() : null; + + final Runnable hbmChangeCallback = () -> { + updateAutoBrightness(true /*sendUpdate*/, false /*userInitiatedChange*/); + // TODO: b/175937645 - Callback to DisplayManagerService to indicate a change to the HBM + // allowance has been made so that the brightness limits can be calculated + // appropriately. + }; + mHbmController = new HighBrightnessModeController(mHandler, brightnessMin, brightnessMax, + hbmData, hbmChangeCallback); } /** @@ -538,6 +556,7 @@ class AutomaticBrightnessController { mAmbientLux = lux; mAmbientBrighteningThreshold = mAmbientBrightnessThresholds.getBrighteningThreshold(lux); mAmbientDarkeningThreshold = mAmbientBrightnessThresholds.getDarkeningThreshold(lux); + mHbmController.onAmbientLuxChange(mAmbientLux); // If the short term model was invalidated and the change is drastic enough, reset it. if (!mShortTermModelValid && mShortTermModelAnchor != -1) { @@ -751,6 +770,7 @@ class AutomaticBrightnessController { mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness)); mScreenDarkeningThreshold = clampScreenBrightness( mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness)); + mHbmController.onAutoBrightnessChanged(mScreenAutoBrightness); if (sendUpdate) { mCallbacks.updateBrightness(); @@ -761,7 +781,7 @@ class AutomaticBrightnessController { // Clamps values with float range [0.0-1.0] private float clampScreenBrightness(float value) { return MathUtils.constrain(value, - mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum); + mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax()); } private void prepareBrightnessAdjustmentSample() { diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 3516981c92a6..cd17cfef2726 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -37,7 +37,6 @@ abstract class DisplayDevice { private final DisplayAdapter mDisplayAdapter; private final IBinder mDisplayToken; private final String mUniqueId; - private DisplayDeviceConfig mDisplayDeviceConfig; // The display device does not manage these properties itself, they are set by // the display manager service. The display device shouldn't really be looking at these. @@ -71,12 +70,11 @@ abstract class DisplayDevice { /* * Gets the DisplayDeviceConfig for this DisplayDevice. - * Returns null for this device but is overridden in LocalDisplayDevice. * - * @return The DisplayDeviceConfig. + * @return The DisplayDeviceConfig; {@code null} if not overridden. */ public DisplayDeviceConfig getDisplayDeviceConfig() { - return mDisplayDeviceConfig; + return null; } /** diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 0a30e07165f9..68708d36f259 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -16,6 +16,7 @@ package com.android.server.display; +import android.annotation.NonNull; import android.content.Context; import android.os.Environment; import android.os.PowerManager; @@ -25,6 +26,8 @@ import android.view.DisplayAddress; import com.android.internal.BrightnessSynchronizer; import com.android.server.display.config.DisplayConfiguration; import com.android.server.display.config.DisplayQuirks; +import com.android.server.display.config.HbmTiming; +import com.android.server.display.config.HighBrightnessMode; import com.android.server.display.config.NitsMap; import com.android.server.display.config.Point; import com.android.server.display.config.XmlParser; @@ -61,19 +64,20 @@ public class DisplayDeviceConfig { private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d"; private static final String NO_SUFFIX_FORMAT = "%d"; private static final long STABLE_FLAG = 1L << 62; - // Float.NaN (used as invalid for brightness) cannot be stored in config.xml // so -2 is used instead private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f; + private final Context mContext; + private float[] mNits; private float[] mBrightness; private float mBrightnessMinimum = Float.NaN; private float mBrightnessMaximum = Float.NaN; private float mBrightnessDefault = Float.NaN; private List<String> mQuirks; - - private final Context mContext; + private boolean mIsHighBrightnessModeEnabled = false; + private HighBrightnessModeData mHbmData; private DisplayDeviceConfig(Context context) { mContext = context; @@ -182,6 +186,19 @@ public class DisplayDeviceConfig { return mQuirks != null && mQuirks.contains(quirkValue); } + /** + * @return high brightness mode configuration data for the display. + */ + public HighBrightnessModeData getHighBrightnessModeData() { + if (!mIsHighBrightnessModeEnabled || mHbmData == null) { + return null; + } + + HighBrightnessModeData hbmData = new HighBrightnessModeData(); + mHbmData.copyTo(hbmData); + return hbmData; + } + @Override public String toString() { String str = "DisplayDeviceConfig{" @@ -191,10 +208,16 @@ public class DisplayDeviceConfig { + ", mBrightnessMaximum=" + mBrightnessMaximum + ", mBrightnessDefault=" + mBrightnessDefault + ", mQuirks=" + mQuirks + + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled + + ", mHbmData=" + mHbmData + "}"; return str; } + private float getMaxBrightness() { + return mBrightness[mBrightness.length - 1]; + } + private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory, String suffixFormat, long idNumber) { @@ -240,6 +263,7 @@ public class DisplayDeviceConfig { loadBrightnessMap(config); loadBrightnessDefaultFromDdcXml(config); loadBrightnessConstraintsFromConfigXml(); + loadHighBrightnessModeData(config); loadQuirks(config); } else { Slog.w(TAG, "DisplayDeviceConfig file is null"); @@ -353,4 +377,66 @@ public class DisplayDeviceConfig { mQuirks = new ArrayList<>(quirks.getQuirk()); } } + + private void loadHighBrightnessModeData(DisplayConfiguration config) { + final HighBrightnessMode hbm = config.getHighBrightnessMode(); + if (hbm != null) { + mIsHighBrightnessModeEnabled = hbm.getEnabled(); + mHbmData = new HighBrightnessModeData(); + mHbmData.minimumLux = hbm.getMinimumLux_all().floatValue(); + mHbmData.transitionPoint = hbm.getTransitionPoint_all().floatValue(); + if (mHbmData.transitionPoint >= getMaxBrightness()) { + throw new IllegalArgumentException("HBM transition point invalid. " + + mHbmData.transitionPoint + " is not less than " + + getMaxBrightness()); + } + final HbmTiming hbmTiming = hbm.getTiming_all(); + mHbmData.timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000; + mHbmData.timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000; + mHbmData.timeMinMillis = hbmTiming.getTimeMinSecs_all().longValue() * 1000; + } + } + + /** + * Container for high brightness mode configuration data. + */ + static class HighBrightnessModeData { + /** Minimum lux needed to enter high brightness mode */ + public float minimumLux; + + /** Brightness level at which we transition from normal to high-brightness. */ + public float transitionPoint; + + /** Time window for HBM. */ + public long timeWindowMillis; + + /** Maximum time HBM is allowed to be during in a {@code timeWindowMillis}. */ + public long timeMaxMillis; + + /** Minimum time that HBM can be on before being enabled. */ + public long timeMinMillis; + + /** + * Copies the HBM data to the specified parameter instance. + * @param other the instance to copy data to. + */ + public void copyTo(@NonNull HighBrightnessModeData other) { + other.minimumLux = minimumLux; + other.transitionPoint = transitionPoint; + other.timeWindowMillis = timeWindowMillis; + other.timeMaxMillis = timeMaxMillis; + other.timeMinMillis = timeMinMillis; + } + + @Override + public String toString() { + return "HBM{" + + "minLux: " + minimumLux + + ", transition: " + transitionPoint + + ", timeWindow: " + timeWindowMillis + "ms" + + ", timeMax: " + timeMaxMillis + "ms" + + ", timeMin: " + timeMinMillis + + "} "; + } + } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 6fa244e2d9ee..60e4595a7679 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1100,7 +1100,7 @@ public final class DisplayManagerService extends SystemService { recordStableDisplayStatsIfNeededLocked(display); recordTopInsetLocked(display); } - addDisplayPowerControllerLocked(displayId); + addDisplayPowerControllerLocked(display); mDisplayStates.append(displayId, Display.STATE_OFF); mDisplayBrightnesses.append(displayId, display.getDisplayInfoLocked().brightnessDefault); @@ -1132,6 +1132,11 @@ public final class DisplayManagerService extends SystemService { // this point. sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); scheduleTraversalLocked(false); + + DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); + if (dpc != null) { + dpc.onDisplayChanged(); + } } private void handleLogicalDisplayFrameRateOverridesChangedLocked( @@ -1860,18 +1865,18 @@ public final class DisplayManagerService extends SystemService { private void initializeDisplayPowerControllersLocked() { mLogicalDisplayMapper.forEachLocked((logicalDisplay) -> addDisplayPowerControllerLocked( - logicalDisplay.getDisplayIdLocked())); + logicalDisplay)); } - private void addDisplayPowerControllerLocked(int displayId) { + private void addDisplayPowerControllerLocked(LogicalDisplay display) { if (mPowerHandler == null) { // initPowerManagement has not yet been called. return; } final DisplayPowerController displayPowerController = new DisplayPowerController( mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, - mDisplayBlanker, mLogicalDisplayMapper.getLocked(displayId)); - mDisplayPowerControllers.append(displayId, displayPowerController); + mDisplayBlanker, display); + mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController); } private final class DisplayManagerHandler extends Handler { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index e31704f03cd9..811625b06cf0 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -523,7 +523,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, - screenBrightnessThresholds, context); + screenBrightnessThresholds, logicalDisplay, context); } else { mUseSoftwareAutoBrightnessConfig = false; } @@ -674,6 +674,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return mAutomaticBrightnessController.getDefaultConfig(); } + /** + * Notified when the display is changed. We use this to apply any changes that might be needed + * when displays get swapped on foldable devices. For example, different brightness properties + * of each display need to be properly reflected in AutomaticBrightnessController. + */ + public void onDisplayChanged() { + // TODO: b/175821789 - Support high brightness on multiple (folding) displays + } + private void sendUpdatePowerState() { synchronized (mLock) { sendUpdatePowerStateLocked(); diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java new file mode 100644 index 000000000000..12b810f906f2 --- /dev/null +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.os.Handler; +import android.os.PowerManager; +import android.os.SystemClock; +import android.util.Slog; + +import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; + +import java.util.Iterator; +import java.util.LinkedList; + +/** + * Controls the status of high-brightness mode for devices that support it. This class assumes that + * an instance is always created even if a device does not support high-brightness mode (HBM); in + * the case where it is not supported, the majority of the logic is skipped. On devices that support + * HBM, we keep track of the ambient lux as well as historical usage of HBM to determine when HBM is + * allowed and not. This class's output is simply a brightness-range maximum value (queried via + * {@link #getCurrentBrightnessMax}) that changes depending on whether HBM is enabled or not. + */ +class HighBrightnessModeController { + private static final String TAG = "HighBrightnessModeController"; + + private static final boolean DEBUG_HBM = false; + + private final float mBrightnessMin; + private final float mBrightnessMax; + private final HighBrightnessModeData mHbmData; + private final Handler mHandler; + private final Runnable mHbmChangeCallback; + private final Runnable mRecalcRunnable; + + private boolean mIsInAllowedAmbientRange = false; + private boolean mIsTimeAvailable = false; + private float mAutoBrightness; + + /** + * If HBM is currently running, this is the start time for the current HBM session. + */ + private long mRunningStartTimeMillis = -1; + + /** + * List of previous HBM-events ordered from most recent to least recent. + * Meant to store only the events that fall into the most recent + * {@link mHbmData.timeWindowSecs}. + */ + private LinkedList<HbmEvent> mEvents = new LinkedList<>(); + + HighBrightnessModeController(Handler handler, float brightnessMin, float brightnessMax, + HighBrightnessModeData hbmData, Runnable hbmChangeCallback) { + mHandler = handler; + mBrightnessMin = brightnessMin; + mBrightnessMax = brightnessMax; + mHbmData = hbmData; + mHbmChangeCallback = hbmChangeCallback; + mAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + + mRecalcRunnable = () -> { + boolean oldIsAllowed = isCurrentlyAllowed(); + recalculateTimeAllowance(); + if (oldIsAllowed != isCurrentlyAllowed()) { + // Our allowed state has changed; tell AutomaticBrightnessController + // to update the brightness. + if (mHbmChangeCallback != null) { + mHbmChangeCallback.run(); + } + } + }; + } + + float getCurrentBrightnessMin() { + return mBrightnessMin; + } + + float getCurrentBrightnessMax() { + if (!deviceSupportsHbm() || isCurrentlyAllowed()) { + // Either the device doesn't support HBM, or HBM range is currently allowed (device + // it in a high-lux environment). In either case, return the highest brightness + // level supported by the device. + return mBrightnessMax; + } else { + // Hbm is not allowed, only allow up to the brightness where we + // transition to high brightness mode. + return mHbmData.transitionPoint; + } + } + + void onAmbientLuxChange(float ambientLux) { + if (!deviceSupportsHbm()) { + return; + } + + final boolean isHighLux = (ambientLux >= mHbmData.minimumLux); + if (isHighLux != mIsInAllowedAmbientRange) { + mIsInAllowedAmbientRange = isHighLux; + recalculateTimeAllowance(); + } + } + + void onAutoBrightnessChanged(float autoBrightness) { + if (!deviceSupportsHbm()) { + return; + } + final float oldAutoBrightness = mAutoBrightness; + mAutoBrightness = autoBrightness; + + // If we are starting or ending a high brightness mode session, store the current + // session in mRunningStartTimeMillis, or the old one in mEvents. + final boolean wasOldBrightnessHigh = oldAutoBrightness > mHbmData.transitionPoint; + final boolean isNewBrightnessHigh = mAutoBrightness > mHbmData.transitionPoint; + if (wasOldBrightnessHigh != isNewBrightnessHigh) { + final long currentTime = SystemClock.uptimeMillis(); + if (isNewBrightnessHigh) { + mRunningStartTimeMillis = currentTime; + } else { + mEvents.addFirst(new HbmEvent(mRunningStartTimeMillis, currentTime)); + mRunningStartTimeMillis = -1; + + if (DEBUG_HBM) { + Slog.d(TAG, "New HBM event: " + mEvents.getFirst()); + } + } + } + + recalculateTimeAllowance(); + } + + private boolean isCurrentlyAllowed() { + return mIsTimeAvailable && mIsInAllowedAmbientRange; + } + + private boolean deviceSupportsHbm() { + return mHbmData != null; + } + + /** + * Recalculates the allowable HBM time. + */ + private void recalculateTimeAllowance() { + final long currentTime = SystemClock.uptimeMillis(); + long timeAlreadyUsed = 0; + + // First, lets see how much time we've taken for any currently running + // session of HBM. + if (mRunningStartTimeMillis > 0) { + if (mRunningStartTimeMillis > currentTime) { + Slog.e(TAG, "Start time set to the future. curr: " + currentTime + + ", start: " + mRunningStartTimeMillis); + mRunningStartTimeMillis = currentTime; + } + timeAlreadyUsed = currentTime - mRunningStartTimeMillis; + } + + if (DEBUG_HBM) { + Slog.d(TAG, "Time already used after current session: " + timeAlreadyUsed); + } + + // Next, lets iterate through the history of previous sessions and add those times. + final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis; + Iterator<HbmEvent> it = mEvents.iterator(); + while (it.hasNext()) { + final HbmEvent event = it.next(); + + // If this event ended before the current Timing window, discard forever and ever. + if (event.endTimeMillis < windowstartTimeMillis) { + it.remove(); + continue; + } + + final long startTimeMillis = Math.max(event.startTimeMillis, windowstartTimeMillis); + timeAlreadyUsed += event.endTimeMillis - startTimeMillis; + } + + if (DEBUG_HBM) { + Slog.d(TAG, "Time already used after all sessions: " + timeAlreadyUsed); + } + + // See how much allowable time we have left. + final long remainingTime = Math.max(0, mHbmData.timeMaxMillis - timeAlreadyUsed); + + // We allow HBM if there is more than the minimum required time available + // or if brightness is already in the high range, if there is any time left at all. + final boolean isAllowedWithoutRestrictions = remainingTime >= mHbmData.timeMinMillis; + final boolean isOnlyAllowedToStayOn = !isAllowedWithoutRestrictions + && remainingTime > 0 && mAutoBrightness > mHbmData.transitionPoint; + mIsTimeAvailable = isAllowedWithoutRestrictions || isOnlyAllowedToStayOn; + + // Calculate the time at which we want to recalculate mIsTimeAvailable in case a lux or + // brightness change doesn't happen before then. + long nextTimeout = -1; + if (mAutoBrightness > mHbmData.transitionPoint) { + // if we're in high-lux now, timeout when we run out of allowed time. + nextTimeout = currentTime + remainingTime; + } else if (!mIsTimeAvailable && mEvents.size() > 0) { + // If we are not allowed...timeout when the oldest event moved outside of the timing + // window by at least minTime. Basically, we're calculating the soonest time we can + // get {@code timeMinMillis} back to us. + final HbmEvent lastEvent = mEvents.getLast(); + final long startTimePlusMinMillis = + Math.max(windowstartTimeMillis, lastEvent.startTimeMillis) + + mHbmData.timeMinMillis; + final long timeWhenMinIsGainedBack = + currentTime + (startTimePlusMinMillis - windowstartTimeMillis) - remainingTime; + nextTimeout = timeWhenMinIsGainedBack; + } + + if (DEBUG_HBM) { + Slog.d(TAG, "HBM recalculated. IsAllowedWithoutRestrictions: " + + isAllowedWithoutRestrictions + + ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn + + ", remainingAllowedTime: " + remainingTime + + ", isLuxHigh: " + mIsInAllowedAmbientRange + + ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed() + + ", brightness: " + mAutoBrightness + + ", RunningStartTimeMillis: " + mRunningStartTimeMillis + + ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1) + + ", events: " + mEvents); + } + + if (nextTimeout != -1) { + mHandler.removeCallbacks(mRecalcRunnable); + mHandler.postAtTime(mRecalcRunnable, nextTimeout); + } + } + + /** + * Represents an event in which High Brightness Mode was enabled. + */ + private static class HbmEvent { + public long startTimeMillis; + public long endTimeMillis; + + HbmEvent(long startTimeMillis, long endTimeMillis) { + this.startTimeMillis = startTimeMillis; + this.endTimeMillis = endTimeMillis; + } + + @Override + public String toString() { + return "[Event: {" + startTimeMillis + ", " + endTimeMillis + "}, total: " + + ((endTimeMillis - startTimeMillis) / 1000) + "]"; + } + } +} 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 000000000000..34ac813f02e0 --- /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/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 52121f352783..0b10cc36ded9 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -752,8 +752,8 @@ abstract class HdmiCecLocalDevice { message.getParams(), false)) { // Vendor command listener may not have been registered yet. Respond with - // <Feature Abort> [NOT_IN_CORRECT_MODE] so that the sender can try again later. - mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); + // <Feature Abort> [Refused] so that the sender can try again later. + mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); } return true; } @@ -764,7 +764,7 @@ abstract class HdmiCecLocalDevice { if (vendorId == mService.getVendorId()) { if (!mService.invokeVendorCommandListenersOnReceived( mDeviceType, message.getSource(), message.getDestination(), params, true)) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); + mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); } } else if (message.getDestination() != Constants.ADDR_BROADCAST && message.getSource() != Constants.ADDR_UNREGISTERED) { diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 372bceeea3c4..7ec4a5adb33e 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -26,6 +26,8 @@ import static android.location.LocationManager.FUSED_PROVIDER; import static android.location.LocationManager.GPS_PROVIDER; import static android.location.LocationManager.NETWORK_PROVIDER; import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS; +import static android.location.provider.LocationProviderBase.ACTION_FUSED_PROVIDER; +import static android.location.provider.LocationProviderBase.ACTION_NETWORK_PROVIDER; import static com.android.server.location.LocationPermissions.PERMISSION_COARSE; import static com.android.server.location.LocationPermissions.PERMISSION_FINE; @@ -45,11 +47,11 @@ import android.content.Intent; import android.location.Criteria; import android.location.GeocoderParams; import android.location.Geofence; +import android.location.GnssAntennaInfo; import android.location.GnssCapabilities; import android.location.GnssMeasurementCorrections; import android.location.GnssMeasurementRequest; import android.location.IGeocodeListener; -import android.location.IGnssAntennaInfoListener; import android.location.IGnssMeasurementsListener; import android.location.IGnssNavigationMessageListener; import android.location.IGnssNmeaListener; @@ -64,7 +66,7 @@ import android.location.LocationManagerInternal; import android.location.LocationProvider; import android.location.LocationRequest; import android.location.LocationTime; -import android.location.ProviderProperties; +import android.location.provider.ProviderProperties; import android.location.util.identity.CallerIdentity; import android.os.Binder; import android.os.Bundle; @@ -213,11 +215,6 @@ public class LocationManagerService extends ILocationManager.Stub { public static final String TAG = "LocationManagerService"; public static final boolean D = Log.isLoggable(TAG, Log.DEBUG); - private static final String NETWORK_LOCATION_SERVICE_ACTION = - "com.android.location.service.v3.NetworkLocationProvider"; - private static final String FUSED_LOCATION_SERVICE_ACTION = - "com.android.location.service.FusedLocationProvider"; - private static final String ATTRIBUTION_TAG = "LocationService"; private final Object mLock = new Object(); @@ -339,7 +336,7 @@ public class LocationManagerService extends ILocationManager.Stub { // provider has unfortunate hard dependencies on the network provider ProxyLocationProvider networkProvider = ProxyLocationProvider.create( mContext, - NETWORK_LOCATION_SERVICE_ACTION, + ACTION_NETWORK_PROVIDER, com.android.internal.R.bool.config_enableNetworkLocationOverlay, com.android.internal.R.string.config_networkLocationProviderPackageName); if (networkProvider != null) { @@ -352,13 +349,13 @@ public class LocationManagerService extends ILocationManager.Stub { // ensure that a fused provider exists which will work in direct boot Preconditions.checkState(!mContext.getPackageManager().queryIntentServicesAsUser( - new Intent(FUSED_LOCATION_SERVICE_ACTION), + new Intent(ACTION_FUSED_PROVIDER), MATCH_DIRECT_BOOT_AWARE | MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM).isEmpty(), "Unable to find a direct boot aware fused location provider"); ProxyLocationProvider fusedProvider = ProxyLocationProvider.create( mContext, - FUSED_LOCATION_SERVICE_ACTION, + ACTION_FUSED_PROVIDER, com.android.internal.R.bool.config_enableFusedLocationOverlay, com.android.internal.R.string.config_fusedLocationProviderPackageName); if (fusedProvider != null) { @@ -410,16 +407,17 @@ public class LocationManagerService extends ILocationManager.Stub { for (String testProviderString : testProviderStrings) { String[] fragments = testProviderString.split(","); String name = fragments[0].trim(); - ProviderProperties properties = new ProviderProperties( - Boolean.parseBoolean(fragments[1]) /* requiresNetwork */, - Boolean.parseBoolean(fragments[2]) /* requiresSatellite */, - Boolean.parseBoolean(fragments[3]) /* requiresCell */, - Boolean.parseBoolean(fragments[4]) /* hasMonetaryCost */, - Boolean.parseBoolean(fragments[5]) /* supportsAltitude */, - Boolean.parseBoolean(fragments[6]) /* supportsSpeed */, - Boolean.parseBoolean(fragments[7]) /* supportsBearing */, - Integer.parseInt(fragments[8]) /* powerUsage */, - Integer.parseInt(fragments[9]) /* accuracy */); + ProviderProperties properties = new ProviderProperties.Builder() + .setHasNetworkRequirement(Boolean.parseBoolean(fragments[1])) + .setHasSatelliteRequirement(Boolean.parseBoolean(fragments[2])) + .setHasCellRequirement(Boolean.parseBoolean(fragments[3])) + .setHasMonetaryCost(Boolean.parseBoolean(fragments[4])) + .setHasAltitudeSupport(Boolean.parseBoolean(fragments[5])) + .setHasSpeedSupport(Boolean.parseBoolean(fragments[6])) + .setHasBearingSupport(Boolean.parseBoolean(fragments[7])) + .setPowerUsage(Integer.parseInt(fragments[8])) + .setAccuracy(Integer.parseInt(fragments[9])) + .build(); getOrAddLocationProviderManager(name).setMockProvider( new MockLocationProvider(properties, CallerIdentity.fromContext(mContext))); } @@ -913,18 +911,8 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public void addGnssAntennaInfoListener(IGnssAntennaInfoListener listener, - String packageName, String attributionTag) { - if (mGnssManagerService != null) { - mGnssManagerService.addGnssAntennaInfoListener(listener, packageName, attributionTag); - } - } - - @Override - public void removeGnssAntennaInfoListener(IGnssAntennaInfoListener listener) { - if (mGnssManagerService != null) { - mGnssManagerService.removeGnssAntennaInfoListener(listener); - } + public List<GnssAntennaInfo> getGnssAntennaInfos() { + return mGnssManagerService == null ? null : mGnssManagerService.getGnssAntennaInfos(); } @Override diff --git a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java deleted file mode 100644 index e9f79efb0513..000000000000 --- a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.location.gnss; - -import static com.android.server.location.gnss.GnssManagerService.D; -import static com.android.server.location.gnss.GnssManagerService.TAG; - -import android.location.GnssAntennaInfo; -import android.location.GnssCapabilities; -import android.location.IGnssAntennaInfoListener; -import android.location.util.identity.CallerIdentity; -import android.util.Log; - -import com.android.server.location.gnss.hal.GnssNative; -import com.android.server.location.injector.Injector; - -import java.util.Collection; -import java.util.List; - -/** - * Provides GNSS antenna information to clients. - */ -public class GnssAntennaInfoProvider extends - GnssListenerMultiplexer<Void, IGnssAntennaInfoListener, Void> implements - GnssNative.BaseCallbacks, GnssNative.AntennaInfoCallbacks { - - private final GnssNative mGnssNative; - - public GnssAntennaInfoProvider(Injector injector, GnssNative gnssNative) { - super(injector); - mGnssNative = gnssNative; - - mGnssNative.addBaseCallbacks(this); - mGnssNative.addAntennaInfoCallbacks(this); - } - - @Override - protected boolean isServiceSupported() { - return mGnssNative.isAntennaInfoListeningSupported(); - } - - @Override - public void addListener(CallerIdentity identity, IGnssAntennaInfoListener listener) { - super.addListener(identity, listener); - } - - @Override - protected boolean registerWithService(Void ignored, - Collection<GnssListenerRegistration> registrations) { - if (mGnssNative.startAntennaInfoListening()) { - if (D) { - Log.d(TAG, "starting gnss antenna info"); - } - return true; - } else { - Log.e(TAG, "error starting gnss antenna info"); - return false; - } - } - - @Override - protected void unregisterWithService() { - if (mGnssNative.stopAntennaInfoListening()) { - if (D) { - Log.d(TAG, "stopping gnss antenna info"); - } - } else { - Log.e(TAG, "error stopping gnss antenna info"); - } - } - - @Override - public void onHalRestarted() { - resetService(); - } - - @Override - public void onCapabilitiesChanged(GnssCapabilities oldCapabilities, - GnssCapabilities newCapabilities) {} - - @Override - public void onReportAntennaInfo(List<GnssAntennaInfo> antennaInfos) { - deliverToListeners(listener -> { - listener.onGnssAntennaInfoReceived(antennaInfos); - }); - } -} diff --git a/services/core/java/com/android/server/location/gnss/GnssGeofenceProxy.java b/services/core/java/com/android/server/location/gnss/GnssGeofenceProxy.java index 32a7952b14d1..f40ded98166c 100644 --- a/services/core/java/com/android/server/location/gnss/GnssGeofenceProxy.java +++ b/services/core/java/com/android/server/location/gnss/GnssGeofenceProxy.java @@ -16,7 +16,6 @@ package com.android.server.location.gnss; -import android.location.GnssCapabilities; import android.location.IGpsGeofenceHardware; import android.util.SparseArray; @@ -141,8 +140,4 @@ class GnssGeofenceProxy extends IGpsGeofenceHardware.Stub implements GnssNative. } } } - - @Override - public void onCapabilitiesChanged(GnssCapabilities oldCapabilities, - GnssCapabilities newCapabilities) {} } diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java index 7e848e03c3a0..87e6ef4110ee 100644 --- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java @@ -96,12 +96,29 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter } @Override - protected void onBinderListenerRegister() { + protected final void onBinderListenerRegister() { mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE, getIdentity()); mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid()); + + onGnssListenerRegister(); } + @Override + protected final void onBinderListenerUnregister() { + onGnssListenerUnregister(); + } + + /** + * May be overridden in place of {@link #onBinderListenerRegister()}. + */ + protected void onGnssListenerRegister() {} + + /** + * May be overridden in place of {@link #onBinderListenerUnregister()}. + */ + protected void onGnssListenerUnregister() {} + boolean onLocationPermissionsChanged(String packageName) { if (getIdentity().getPackageName().equals(packageName)) { return onLocationPermissionsChanged(); diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index afe75674647f..d16267f0582e 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -16,6 +16,9 @@ package com.android.server.location.gnss; +import static android.location.provider.ProviderProperties.ACCURACY_FINE; +import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH; + import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_GSM_CELLID; import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_UMTS_CELLID; @@ -50,7 +53,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; -import android.location.Criteria; import android.location.GnssCapabilities; import android.location.GnssStatus; import android.location.INetInitiatedListener; @@ -59,7 +61,8 @@ import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationRequest; import android.location.LocationResult; -import android.location.ProviderProperties; +import android.location.provider.ProviderProperties; +import android.location.provider.ProviderRequest; import android.location.util.identity.CallerIdentity; import android.os.AsyncTask; import android.os.BatteryStats; @@ -89,7 +92,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.location.GpsNetInitiatedHandler; import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; -import com.android.internal.location.ProviderRequest; import com.android.internal.util.FrameworkStatsLog; import com.android.server.FgThread; import com.android.server.location.gnss.GnssSatelliteBlocklistHelper.GnssSatelliteBlocklistCallback; @@ -123,16 +125,14 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); - private static final ProviderProperties PROPERTIES = new ProviderProperties( - /* requiresNetwork = */false, - /* requiresSatellite = */true, - /* requiresCell = */false, - /* hasMonetaryCost = */false, - /* supportAltitude = */true, - /* supportsSpeed = */true, - /* supportsBearing = */true, - ProviderProperties.POWER_USAGE_HIGH, - ProviderProperties.ACCURACY_FINE); + private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder() + .setHasSatelliteRequirement(true) + .setHasAltitudeSupport(true) + .setHasSpeedSupport(true) + .setHasBearingSupport(true) + .setPowerUsage(POWER_USAGE_HIGH) + .setAccuracy(ACCURACY_FINE) + .build(); // The AGPS SUPL mode private static final int AGPS_SUPL_MODE_MSA = 0x02; diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index ff9244464069..92957aaf2976 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -19,23 +19,27 @@ package com.android.server.location.gnss; import android.Manifest; import android.annotation.Nullable; import android.content.Context; +import android.content.Intent; import android.hardware.location.GeofenceHardware; import android.hardware.location.GeofenceHardwareImpl; import android.location.FusedBatchOptions; +import android.location.GnssAntennaInfo; import android.location.GnssCapabilities; import android.location.GnssMeasurementCorrections; import android.location.GnssMeasurementRequest; -import android.location.IGnssAntennaInfoListener; import android.location.IGnssMeasurementsListener; import android.location.IGnssNavigationMessageListener; import android.location.IGnssNmeaListener; import android.location.IGnssStatusListener; import android.location.IGpsGeofenceHardware; import android.location.Location; +import android.location.LocationManager; import android.location.util.identity.CallerIdentity; import android.os.BatteryStats; +import android.os.Binder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.IndentingPrintWriter; import android.util.Log; @@ -45,6 +49,8 @@ import com.android.server.location.gnss.hal.GnssNative; import com.android.server.location.injector.Injector; import java.io.FileDescriptor; +import java.util.ArrayList; +import java.util.List; /** Manages Gnss providers and related Gnss functions for LocationManagerService. */ public class GnssManagerService { @@ -61,10 +67,13 @@ public class GnssManagerService { private final GnssStatusProvider mGnssStatusProvider; private final GnssNmeaProvider mGnssNmeaProvider; private final GnssMeasurementsProvider mGnssMeasurementsProvider; - private final GnssAntennaInfoProvider mGnssAntennaInfoProvider; private final GnssNavigationMessageProvider mGnssNavigationMessageProvider; private final IGpsGeofenceHardware mGnssGeofenceProxy; + private final GnssGeofenceHalModule mGeofenceHalModule; + private final GnssCapabilitiesHalModule mCapabilitiesHalModule; + private final GnssAntennaInfoHalModule mAntennaInfoHalModule; + private final GnssMetrics mGnssMetrics; public GnssManagerService(Context context, Injector injector, GnssNative gnssNative) { @@ -79,11 +88,12 @@ public class GnssManagerService { mGnssStatusProvider = new GnssStatusProvider(injector, mGnssNative); mGnssNmeaProvider = new GnssNmeaProvider(injector, mGnssNative); mGnssMeasurementsProvider = new GnssMeasurementsProvider(injector, mGnssNative); - mGnssAntennaInfoProvider = new GnssAntennaInfoProvider(injector, mGnssNative); mGnssNavigationMessageProvider = new GnssNavigationMessageProvider(injector, mGnssNative); mGnssGeofenceProxy = new GnssGeofenceProxy(mGnssNative); - mGnssNative.setGeofenceCallbacks(new GnssGeofenceHalModule()); + mGeofenceHalModule = new GnssGeofenceHalModule(mGnssNative); + mCapabilitiesHalModule = new GnssCapabilitiesHalModule(mGnssNative); + mAntennaInfoHalModule = new GnssAntennaInfoHalModule(mGnssNative); // allow gnss access to begin - we must assume that callbacks can start immediately mGnssNative.register(); @@ -120,14 +130,20 @@ public class GnssManagerService { } /** - * Get GNSS hardware capabilities. The capabilities returned are a bitfield as described in - * {@link android.location.GnssCapabilities}. + * Get GNSS hardware capabilities. */ public GnssCapabilities getGnssCapabilities() { return mGnssNative.getCapabilities(); } /** + * Get GNSS antenna information. + */ + public @Nullable List<GnssAntennaInfo> getGnssAntennaInfos() { + return mAntennaInfoHalModule.getAntennaInfos(); + } + + /** * Get size of GNSS batch (GNSS location results are batched together for power savings). */ public int getGnssBatchSize() { @@ -202,29 +218,6 @@ public class GnssManagerService { } /** - * Adds a GNSS Antenna Info listener. - * - * @param listener called when GNSS antenna info is received - * @param packageName name of requesting package - */ - public void addGnssAntennaInfoListener(IGnssAntennaInfoListener listener, String packageName, - @Nullable String attributionTag) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null); - - CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); - mGnssAntennaInfoProvider.addListener(identity, listener); - } - - /** - * Removes a GNSS Antenna Info listener. - * - * @param listener called when GNSS antenna info is received - */ - public void removeGnssAntennaInfoListener(IGnssAntennaInfoListener listener) { - mGnssAntennaInfoProvider.removeListener(listener); - } - - /** * Adds a GNSS navigation message listener. */ public void addGnssNavigationMessageListener(IGnssNavigationMessageListener listener, @@ -264,12 +257,12 @@ public class GnssManagerService { ipw.println("Capabilities: " + mGnssNative.getCapabilities()); - ipw.println("Antenna Info Provider:"); - ipw.increaseIndent(); - mGnssAntennaInfoProvider.dump(fd, ipw, args); - ipw.decreaseIndent(); + List<GnssAntennaInfo> infos = mAntennaInfoHalModule.getAntennaInfos(); + if (infos != null) { + ipw.println("Antenna Infos: " + infos); + } - ipw.println("Measurement Provider:"); + ipw.println("Measurements Provider:"); ipw.increaseIndent(); mGnssMeasurementsProvider.dump(fd, ipw, args); ipw.decreaseIndent(); @@ -293,10 +286,39 @@ public class GnssManagerService { } } + private class GnssCapabilitiesHalModule implements GnssNative.BaseCallbacks { + + GnssCapabilitiesHalModule(GnssNative gnssNative) { + gnssNative.addBaseCallbacks(this); + } + + @Override + public void onHalRestarted() {} + + @Override + public void onCapabilitiesChanged(GnssCapabilities oldCapabilities, + GnssCapabilities newCapabilities) { + long ident = Binder.clearCallingIdentity(); + try { + Intent intent = new Intent(LocationManager.ACTION_GNSS_CAPABILITIES_CHANGED) + .putExtra(LocationManager.EXTRA_GNSS_CAPABILITIES, newCapabilities) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + private class GnssGeofenceHalModule implements GnssNative.GeofenceCallbacks { private GeofenceHardwareImpl mGeofenceHardwareImpl; + GnssGeofenceHalModule(GnssNative gnssNative) { + gnssNative.setGeofenceCallbacks(this); + } + private synchronized GeofenceHardwareImpl getGeofenceHardware() { if (mGeofenceHardwareImpl == null) { mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); @@ -371,4 +393,53 @@ public class GnssManagerService { } } } + + private class GnssAntennaInfoHalModule implements GnssNative.BaseCallbacks, + GnssNative.AntennaInfoCallbacks { + + private final GnssNative mGnssNative; + + private volatile @Nullable List<GnssAntennaInfo> mAntennaInfos; + + GnssAntennaInfoHalModule(GnssNative gnssNative) { + mGnssNative = gnssNative; + mGnssNative.addBaseCallbacks(this); + mGnssNative.addAntennaInfoCallbacks(this); + } + + @Nullable List<GnssAntennaInfo> getAntennaInfos() { + return mAntennaInfos; + } + + @Override + public void onHalStarted() { + mGnssNative.startAntennaInfoListening(); + } + + @Override + public void onHalRestarted() { + mGnssNative.startAntennaInfoListening(); + } + + @Override + public void onReportAntennaInfo(List<GnssAntennaInfo> antennaInfos) { + if (antennaInfos.equals(mAntennaInfos)) { + return; + } + + mAntennaInfos = antennaInfos; + + long ident = Binder.clearCallingIdentity(); + try { + Intent intent = new Intent(LocationManager.ACTION_GNSS_ANTENNA_INFOS_CHANGED) + .putParcelableArrayListExtra(LocationManager.EXTRA_GNSS_ANTENNA_INFOS, + new ArrayList<>(antennaInfos)) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } } diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java index b7cc9f51bfec..b623e279d9ae 100644 --- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java @@ -21,7 +21,6 @@ import static com.android.server.location.gnss.GnssManagerService.TAG; import android.annotation.Nullable; import android.app.AppOpsManager; -import android.location.GnssCapabilities; import android.location.GnssMeasurementRequest; import android.location.GnssMeasurementsEvent; import android.location.IGnssMeasurementsListener; @@ -58,8 +57,14 @@ public final class GnssMeasurementsProvider extends protected GnssMeasurementListenerRegistration( @Nullable GnssMeasurementRequest request, CallerIdentity callerIdentity, - IGnssMeasurementsListener iGnssMeasurementsListener) { - super(request, callerIdentity, iGnssMeasurementsListener); + IGnssMeasurementsListener listener) { + super(request, callerIdentity, listener); + } + + @Override + protected void onGnssListenerRegister() { + executeOperation(listener -> listener.onStatusChanged( + GnssMeasurementsEvent.Callback.STATUS_READY)); } @Nullable @@ -201,10 +206,6 @@ public final class GnssMeasurementsProvider extends } @Override - public void onCapabilitiesChanged(GnssCapabilities oldCapabilities, - GnssCapabilities newCapabilities) {} - - @Override public void onReportMeasurements(GnssMeasurementsEvent event) { deliverToListeners(registration -> { if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, diff --git a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java index 9b1cde0d967c..ff9ad65b4ca5 100644 --- a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java @@ -20,7 +20,6 @@ import static com.android.server.location.gnss.GnssManagerService.D; import static com.android.server.location.gnss.GnssManagerService.TAG; import android.app.AppOpsManager; -import android.location.GnssCapabilities; import android.location.GnssNavigationMessage; import android.location.IGnssNavigationMessageListener; import android.location.util.identity.CallerIdentity; @@ -43,6 +42,21 @@ public class GnssNavigationMessageProvider extends GnssListenerMultiplexer<Void, IGnssNavigationMessageListener, Void> implements GnssNative.BaseCallbacks, GnssNative.NavigationMessageCallbacks { + private class GnssNavigationMessageListenerRegistration extends GnssListenerRegistration { + + protected GnssNavigationMessageListenerRegistration( + CallerIdentity callerIdentity, + IGnssNavigationMessageListener listener) { + super(null, callerIdentity, listener); + } + + @Override + protected void onGnssListenerRegister() { + executeOperation(listener -> listener.onStatusChanged( + GnssNavigationMessage.Callback.STATUS_READY)); + } + } + private final AppOpsHelper mAppOpsHelper; private final GnssNative mGnssNative; @@ -66,6 +80,12 @@ public class GnssNavigationMessageProvider extends } @Override + protected GnssListenerRegistration createRegistration(Void request, + CallerIdentity callerIdentity, IGnssNavigationMessageListener listener) { + return new GnssNavigationMessageListenerRegistration(callerIdentity, listener); + } + + @Override protected boolean registerWithService(Void ignored, Collection<GnssListenerRegistration> registrations) { if (mGnssNative.startNavigationMessageCollection()) { @@ -96,10 +116,6 @@ public class GnssNavigationMessageProvider extends } @Override - public void onCapabilitiesChanged(GnssCapabilities oldCapabilities, - GnssCapabilities newCapabilities) {} - - @Override public void onReportNavigationMessage(GnssNavigationMessage event) { deliverToListeners(registration -> { if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, diff --git a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java index bad1b79e3568..5036a6e7edf5 100644 --- a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java @@ -21,7 +21,6 @@ import static com.android.server.location.gnss.GnssManagerService.TAG; import android.annotation.Nullable; import android.app.AppOpsManager; -import android.location.GnssCapabilities; import android.location.IGnssNmeaListener; import android.location.util.identity.CallerIdentity; import android.util.Log; @@ -83,10 +82,6 @@ class GnssNmeaProvider extends GnssListenerMultiplexer<Void, IGnssNmeaListener, } @Override - public void onCapabilitiesChanged(GnssCapabilities oldCapabilities, - GnssCapabilities newCapabilities) {} - - @Override public void onReportNmea(long timestamp) { deliverToListeners( new Function<GnssListenerRegistration, diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java index e0673db274e5..1eb16184685d 100644 --- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java @@ -20,7 +20,6 @@ import static com.android.server.location.gnss.GnssManagerService.D; import static com.android.server.location.gnss.GnssManagerService.TAG; import android.app.AppOpsManager; -import android.location.GnssCapabilities; import android.location.GnssStatus; import android.location.IGnssStatusListener; import android.location.util.identity.CallerIdentity; @@ -111,10 +110,6 @@ public class GnssStatusProvider extends } @Override - public void onCapabilitiesChanged(GnssCapabilities oldCapabilities, - GnssCapabilities newCapabilities) {} - - @Override public void onReportStatus(@GnssNative.StatusCallbacks.GnssStatusValue int gnssStatus) { boolean isNavigating; switch (gnssStatus) { diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java index 89d82495dca3..402e84b959c6 100644 --- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java +++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java @@ -141,9 +141,10 @@ public class GnssNative { /** Callbacks relevant to the entire HAL. */ public interface BaseCallbacks { + default void onHalStarted() {} void onHalRestarted(); - void onCapabilitiesChanged(GnssCapabilities oldCapabilities, - GnssCapabilities newCapabilities); + default void onCapabilitiesChanged(GnssCapabilities oldCapabilities, + GnssCapabilities newCapabilities) {} } /** Callbacks for status events. */ @@ -376,6 +377,7 @@ public class GnssNative { private volatile boolean mItarSpeedLimitExceeded; private GnssCapabilities mCapabilities = new GnssCapabilities.Builder().build(); + private @GnssCapabilities.TopHalCapabilityFlags int mTopFlags; private @Nullable GnssPowerStats mPowerStats = null; private int mHardwareYear = 0; private @Nullable String mHardwareModelName = null; @@ -480,10 +482,16 @@ public class GnssNative { mRegistered = true; initializeGnss(false); + Log.i(TAG, "gnss hal started"); + + for (int i = 0; i < mBaseCallbacks.length; i++) { + mBaseCallbacks[i].onHalStarted(); + } } private void initializeGnss(boolean restart) { Preconditions.checkState(mRegistered); + mTopFlags = 0; mGnssHal.initOnce(GnssNative.this, restart); // gnss chipset appears to require an init/cleanup cycle on startup in order to properly @@ -1025,8 +1033,12 @@ public class GnssNative { @NativeEntryPoint void setTopHalCapabilities(@GnssCapabilities.TopHalCapabilityFlags int capabilities) { + // Here the bits specified by 'capabilities' are turned on. It is handled differently from + // sub hal because top hal capabilities could be set by HIDL HAL and/or AIDL HAL. Each of + // them possesses a different set of capabilities. + mTopFlags |= capabilities; GnssCapabilities oldCapabilities = mCapabilities; - mCapabilities = oldCapabilities.withTopHalFlags(capabilities); + mCapabilities = oldCapabilities.withTopHalFlags(mTopFlags); onCapabilitiesChanged(oldCapabilities, mCapabilities); } @@ -1413,6 +1425,8 @@ public class GnssNative { private static native boolean native_stop_navigation_message_collection(); // antenna info APIS + // TODO: in a next version of the HAL, consider removing the necessity for listening to antenna + // info changes, and simply report them always, same as capabilities. private static native boolean native_is_antenna_info_supported(); diff --git a/services/core/java/com/android/server/location/injector/LocationEventLog.java b/services/core/java/com/android/server/location/injector/LocationEventLog.java index b34beedd567e..b8b54b3a42e7 100644 --- a/services/core/java/com/android/server/location/injector/LocationEventLog.java +++ b/services/core/java/com/android/server/location/injector/LocationEventLog.java @@ -26,11 +26,11 @@ import static com.android.server.location.LocationManagerService.D; import android.annotation.Nullable; import android.location.LocationRequest; +import android.location.provider.ProviderRequest; import android.location.util.identity.CallerIdentity; import android.os.Build; import android.os.PowerManager.LocationPowerSaveMode; -import com.android.internal.location.ProviderRequest; import com.android.server.utils.eventlog.LocalEventLog; /** In memory event log for location events. */ diff --git a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java index 5364feb67fa4..e22a0145f2bf 100644 --- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java @@ -18,12 +18,12 @@ package com.android.server.location.provider; import android.annotation.Nullable; import android.location.LocationResult; -import android.location.ProviderProperties; +import android.location.provider.ProviderProperties; +import android.location.provider.ProviderRequest; import android.location.util.identity.CallerIdentity; import android.os.Binder; import android.os.Bundle; -import com.android.internal.location.ProviderRequest; import com.android.internal.util.Preconditions; import java.io.FileDescriptor; diff --git a/services/core/java/com/android/server/location/provider/LocationProviderController.java b/services/core/java/com/android/server/location/provider/LocationProviderController.java index e49d9db274b5..a0e37944affd 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderController.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderController.java @@ -17,9 +17,9 @@ package com.android.server.location.provider; import android.annotation.Nullable; +import android.location.provider.ProviderRequest; import android.os.Bundle; -import com.android.internal.location.ProviderRequest; import com.android.server.location.provider.AbstractLocationProvider.Listener; import com.android.server.location.provider.AbstractLocationProvider.State; diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 858b7624891a..14f010063538 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -55,7 +55,8 @@ import android.location.LocationManagerInternal; import android.location.LocationManagerInternal.ProviderEnabledListener; import android.location.LocationRequest; import android.location.LocationResult; -import android.location.ProviderProperties; +import android.location.provider.ProviderProperties; +import android.location.provider.ProviderRequest; import android.location.util.identity.CallerIdentity; import android.os.Binder; import android.os.Build; @@ -82,7 +83,6 @@ import android.util.SparseBooleanArray; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; -import com.android.internal.location.ProviderRequest; import com.android.internal.util.Preconditions; import com.android.server.FgThread; import com.android.server.LocalServices; diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java index f9aa4020b522..dce7b081de6e 100644 --- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java @@ -21,12 +21,11 @@ import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.annotation.Nullable; import android.location.Location; import android.location.LocationResult; -import android.location.ProviderProperties; +import android.location.provider.ProviderProperties; +import android.location.provider.ProviderRequest; import android.location.util.identity.CallerIdentity; import android.os.Bundle; -import com.android.internal.location.ProviderRequest; - import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java index c1b0abf64820..cb7264e55fa9 100644 --- a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java @@ -21,12 +21,12 @@ import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.annotation.Nullable; import android.location.Location; import android.location.LocationResult; -import android.location.ProviderProperties; +import android.location.provider.ProviderProperties; +import android.location.provider.ProviderRequest; import android.location.util.identity.CallerIdentity; import android.os.Bundle; import com.android.internal.annotations.GuardedBy; -import com.android.internal.location.ProviderRequest; import com.android.internal.util.Preconditions; import java.io.FileDescriptor; diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java index 1f4c4cf2a0ac..a5758a37b983 100644 --- a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java @@ -16,16 +16,18 @@ package com.android.server.location.provider; +import static android.location.provider.ProviderProperties.ACCURACY_FINE; +import static android.location.provider.ProviderProperties.POWER_USAGE_LOW; + import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.content.Context; import android.location.LocationResult; -import android.location.ProviderProperties; +import android.location.provider.ProviderProperties; +import android.location.provider.ProviderRequest; import android.location.util.identity.CallerIdentity; import android.os.Bundle; -import com.android.internal.location.ProviderRequest; - import java.io.FileDescriptor; import java.io.PrintWriter; @@ -38,16 +40,10 @@ import java.io.PrintWriter; */ public class PassiveLocationProvider extends AbstractLocationProvider { - private static final ProviderProperties PROPERTIES = new ProviderProperties( - /* requiresNetwork = */false, - /* requiresSatellite = */false, - /* requiresCell = */false, - /* hasMonetaryCost = */false, - /* supportsAltitude = */false, - /* supportsSpeed = */false, - /* supportsBearing = */false, - ProviderProperties.POWER_USAGE_LOW, - ProviderProperties.ACCURACY_COARSE); + private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder() + .setPowerUsage(POWER_USAGE_LOW) + .setAccuracy(ACCURACY_FINE) + .build(); public PassiveLocationProvider(Context context) { // using a direct executor is ok because this class has no locks that could deadlock diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java index d3fee82ed46a..b35af4f6475c 100644 --- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java @@ -20,9 +20,9 @@ import android.annotation.Nullable; import android.content.Context; import android.location.LocationManager; import android.location.LocationResult; +import android.location.provider.ProviderRequest; import android.os.Binder; -import com.android.internal.location.ProviderRequest; import com.android.internal.util.Preconditions; import com.android.server.location.injector.Injector; diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java index 345fdc00ff85..c274c2848ab6 100644 --- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java @@ -22,7 +22,10 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.location.LocationResult; -import android.location.ProviderProperties; +import android.location.provider.ILocationProvider; +import android.location.provider.ILocationProviderManager; +import android.location.provider.ProviderProperties; +import android.location.provider.ProviderRequest; import android.location.util.identity.CallerIdentity; import android.os.Binder; import android.os.Bundle; @@ -30,9 +33,6 @@ import android.os.IBinder; import android.os.RemoteException; import com.android.internal.annotations.GuardedBy; -import com.android.internal.location.ILocationProvider; -import com.android.internal.location.ILocationProviderManager; -import com.android.internal.location.ProviderRequest; import com.android.internal.util.ArrayUtils; import com.android.server.ServiceWatcher; import com.android.server.location.provider.AbstractLocationProvider; @@ -106,7 +106,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider { ProviderRequest request = mRequest; if (!request.equals(ProviderRequest.EMPTY_REQUEST)) { - provider.setRequest(request, request.getWorkSource()); + provider.setRequest(request); } } } @@ -142,7 +142,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider { mRequest = request; mServiceWatcher.runOnBinder(binder -> { ILocationProvider provider = ILocationProvider.Stub.asInterface(binder); - provider.setRequest(request, request.getWorkSource()); + provider.setRequest(request); }); } diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/location/timezone/ControllerImpl.java index 03ce8ecd1cc0..0d284fc2de4f 100644 --- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java +++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java @@ -73,6 +73,10 @@ class ControllerImpl extends LocationTimeZoneProviderController { // Non-null after initialize() private Callback mCallback; + /** Indicates both providers have completed initialization. */ + @GuardedBy("mSharedLock") + private boolean mProvidersInitialized; + /** * Used for scheduling uncertainty timeouts, i.e after a provider has reported uncertainty. * This timeout is not provider-specific: it is started when the controller becomes uncertain @@ -108,6 +112,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { ControllerImpl.this::onProviderStateChange; mPrimaryProvider.initialize(providerListener); mSecondaryProvider.initialize(providerListener); + mProvidersInitialized = true; alterProvidersStartedStateIfRequired( null /* oldConfiguration */, mCurrentUserConfiguration); @@ -322,6 +327,16 @@ class ControllerImpl extends LocationTimeZoneProviderController { assertProviderKnown(provider); synchronized (mSharedLock) { + // Ignore provider state changes during initialization. e.g. if the primary provider + // moves to PROVIDER_STATE_PERM_FAILED during initialization, the secondary will not + // be ready to take over yet. + if (!mProvidersInitialized) { + warnLog("onProviderStateChange: Ignoring provider state change because both" + + " providers have not yet completed initialization." + + " providerState=" + providerState); + return; + } + switch (providerState.stateEnum) { case PROVIDER_STATE_STARTED_INITIALIZING: case PROVIDER_STATE_STOPPED: @@ -610,6 +625,38 @@ class ControllerImpl extends LocationTimeZoneProviderController { } } + /** + * Sets whether the controller should record provider state changes for later dumping via + * {@link #getStateForTests()}. + */ + void setProviderStateRecordingEnabled(boolean enabled) { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + mPrimaryProvider.setStateChangeRecordingEnabled(enabled); + mSecondaryProvider.setStateChangeRecordingEnabled(enabled); + } + } + + /** + * Returns a snapshot of the current controller state for tests. + */ + @NonNull + LocationTimeZoneManagerServiceState getStateForTests() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + LocationTimeZoneManagerServiceState.Builder builder = + new LocationTimeZoneManagerServiceState.Builder(); + if (mLastSuggestion != null) { + builder.setLastSuggestion(mLastSuggestion); + } + builder.setPrimaryProviderStateChanges(mPrimaryProvider.getRecordedStates()) + .setSecondaryProviderStateChanges(mSecondaryProvider.getRecordedStates()); + return builder.build(); + } + } + @Nullable private LocationTimeZoneProvider getLocationTimeZoneProvider(@NonNull String providerName) { LocationTimeZoneProvider targetProvider; diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java index 9f159fbe7b65..54535eb50130 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java @@ -17,11 +17,10 @@ package com.android.server.location.timezone; import static android.app.time.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME; +import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DISABLED; +import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE; +import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED; import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME; -import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY; -import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY; -import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED; -import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED; import android.annotation.NonNull; import android.annotation.Nullable; @@ -33,7 +32,6 @@ import android.os.Handler; import android.os.RemoteCallback; import android.os.ResultReceiver; import android.os.ShellCallback; -import android.os.SystemProperties; import android.service.timezone.TimeZoneProviderService; import android.util.IndentingPrintWriter; import android.util.Log; @@ -42,6 +40,7 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; +import com.android.internal.util.Preconditions; import com.android.server.FgThread; import com.android.server.SystemService; import com.android.server.timezonedetector.TimeZoneDetectorInternal; @@ -161,6 +160,14 @@ public class LocationTimeZoneManagerService extends Binder { @GuardedBy("mSharedLock") private ControllerEnvironmentImpl mEnvironment; + @GuardedBy("mSharedLock") + @NonNull + private String mPrimaryProviderModeOverride = PROVIDER_MODE_OVERRIDE_NONE; + + @GuardedBy("mSharedLock") + @NonNull + private String mSecondaryProviderModeOverride = PROVIDER_MODE_OVERRIDE_NONE; + LocationTimeZoneManagerService(Context context) { mContext = context.createAttributionContext(ATTRIBUTION_TAG); mHandler = FgThread.getHandler(); @@ -231,9 +238,9 @@ public class LocationTimeZoneManagerService extends Binder { private LocationTimeZoneProvider createPrimaryProvider() { LocationTimeZoneProviderProxy proxy; - if (isInSimulationMode(PRIMARY_PROVIDER_NAME)) { + if (isProviderInSimulationMode(PRIMARY_PROVIDER_NAME)) { proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain); - } else if (isDisabled(PRIMARY_PROVIDER_NAME)) { + } else if (isProviderDisabled(PRIMARY_PROVIDER_NAME)) { proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain); } else { proxy = new RealLocationTimeZoneProviderProxy( @@ -250,9 +257,9 @@ public class LocationTimeZoneManagerService extends Binder { private LocationTimeZoneProvider createSecondaryProvider() { LocationTimeZoneProviderProxy proxy; - if (isInSimulationMode(SECONDARY_PROVIDER_NAME)) { + if (isProviderInSimulationMode(SECONDARY_PROVIDER_NAME)) { proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain); - } else if (isDisabled(SECONDARY_PROVIDER_NAME)) { + } else if (isProviderDisabled(SECONDARY_PROVIDER_NAME)) { proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain); } else { proxy = new RealLocationTimeZoneProviderProxy( @@ -268,16 +275,14 @@ public class LocationTimeZoneManagerService extends Binder { } /** Used for bug triage and in tests to simulate provider events. */ - private static boolean isInSimulationMode(String providerName) { - return isProviderModeSetInSystemProperties(providerName, - SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED); + private boolean isProviderInSimulationMode(String providerName) { + return isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_SIMULATED); } /** Used for bug triage, tests and experiments to remove a provider. */ - private boolean isDisabled(String providerName) { + private boolean isProviderDisabled(String providerName) { return !isProviderEnabledInConfig(providerName) - || isProviderModeSetInSystemProperties( - providerName, SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED); + || isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_DISABLED); } private boolean isProviderEnabledInConfig(String providerName) { @@ -299,25 +304,18 @@ public class LocationTimeZoneManagerService extends Binder { return resources.getBoolean(providerEnabledConfigId); } - private static boolean isProviderModeSetInSystemProperties( - @NonNull String providerName, @NonNull String mode) { - String systemPropertyKey; + private boolean isProviderModeOverrideSet(@NonNull String providerName, @NonNull String mode) { switch (providerName) { case PRIMARY_PROVIDER_NAME: { - systemPropertyKey = SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY; - break; + return Objects.equals(mPrimaryProviderModeOverride, mode); } case SECONDARY_PROVIDER_NAME: { - systemPropertyKey = SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY; - break; + return Objects.equals(mSecondaryProviderModeOverride, mode); } default: { throw new IllegalArgumentException(providerName); } } - - String systemPropertyProviderMode = SystemProperties.get(systemPropertyKey, null); - return Objects.equals(systemPropertyProviderMode, mode); } /** @@ -347,6 +345,67 @@ public class LocationTimeZoneManagerService extends Binder { this, in, out, err, args, callback, resultReceiver); } + /** Sets this service into provider state recording mode for tests. */ + void setProviderModeOverride(@NonNull String providerName, @NonNull String mode) { + enforceManageTimeZoneDetectorPermission(); + + Preconditions.checkArgument( + PRIMARY_PROVIDER_NAME.equals(providerName) + || SECONDARY_PROVIDER_NAME.equals(providerName)); + Preconditions.checkArgument(PROVIDER_MODE_OVERRIDE_DISABLED.equals(mode) + || PROVIDER_MODE_OVERRIDE_SIMULATED.equals(mode) + || PROVIDER_MODE_OVERRIDE_NONE.equals(mode)); + + mThreadingDomain.postAndWait(() -> { + synchronized (mSharedLock) { + switch (providerName) { + case PRIMARY_PROVIDER_NAME: { + mPrimaryProviderModeOverride = mode; + break; + } + case SECONDARY_PROVIDER_NAME: { + mSecondaryProviderModeOverride = mode; + break; + } + } + } + }, BLOCKING_OP_WAIT_DURATION_MILLIS); + } + + /** Sets this service into provider state recording mode for tests. */ + void setProviderStateRecordingEnabled(boolean enabled) { + enforceManageTimeZoneDetectorPermission(); + + mThreadingDomain.postAndWait(() -> { + synchronized (mSharedLock) { + if (mLocationTimeZoneDetectorController != null) { + mLocationTimeZoneDetectorController.setProviderStateRecordingEnabled(enabled); + } + } + }, BLOCKING_OP_WAIT_DURATION_MILLIS); + } + + /** Returns a snapshot of the current controller state for tests. */ + @NonNull + LocationTimeZoneManagerServiceState getStateForTests() { + enforceManageTimeZoneDetectorPermission(); + + try { + return mThreadingDomain.postAndWait( + () -> { + synchronized (mSharedLock) { + if (mLocationTimeZoneDetectorController == null) { + return null; + } + return mLocationTimeZoneDetectorController.getStateForTests(); + } + }, + BLOCKING_OP_WAIT_DURATION_MILLIS); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /** * Passes a {@link TestCommand} to the specified provider and waits for the response. */ diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java new file mode 100644 index 000000000000..b1dd55f3d4fd --- /dev/null +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.timezone; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState; +import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** A snapshot of the location time zone manager service's state for tests. */ +final class LocationTimeZoneManagerServiceState { + + @Nullable private final GeolocationTimeZoneSuggestion mLastSuggestion; + @NonNull private final List<ProviderState> mPrimaryProviderStates; + @NonNull private final List<ProviderState> mSecondaryProviderStates; + + LocationTimeZoneManagerServiceState(@NonNull Builder builder) { + mLastSuggestion = builder.mLastSuggestion; + mPrimaryProviderStates = Objects.requireNonNull(builder.mPrimaryProviderStates); + mSecondaryProviderStates = Objects.requireNonNull(builder.mSecondaryProviderStates); + } + + @Nullable + public GeolocationTimeZoneSuggestion getLastSuggestion() { + return mLastSuggestion; + } + + @NonNull + public List<ProviderState> getPrimaryProviderStates() { + return Collections.unmodifiableList(mPrimaryProviderStates); + } + + @NonNull + public List<ProviderState> getSecondaryProviderStates() { + return Collections.unmodifiableList(mSecondaryProviderStates); + } + + @Override + public String toString() { + return "LocationTimeZoneManagerServiceState{" + + "mLastSuggestion=" + mLastSuggestion + + ", mPrimaryProviderStates=" + mPrimaryProviderStates + + ", mSecondaryProviderStates=" + mSecondaryProviderStates + + '}'; + } + + static final class Builder { + + private GeolocationTimeZoneSuggestion mLastSuggestion; + private List<ProviderState> mPrimaryProviderStates; + private List<ProviderState> mSecondaryProviderStates; + + @NonNull + Builder setLastSuggestion(@NonNull GeolocationTimeZoneSuggestion lastSuggestion) { + mLastSuggestion = Objects.requireNonNull(lastSuggestion); + return this; + } + + @NonNull + Builder setPrimaryProviderStateChanges(@NonNull List<ProviderState> primaryProviderStates) { + mPrimaryProviderStates = new ArrayList<>(primaryProviderStates); + return this; + } + + @NonNull + Builder setSecondaryProviderStateChanges( + @NonNull List<ProviderState> secondaryProviderStates) { + mSecondaryProviderStates = new ArrayList<>(secondaryProviderStates); + return this; + } + + @NonNull + LocationTimeZoneManagerServiceState build() { + return new LocationTimeZoneManagerServiceState(this); + } + } +} diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java index 554df07aa485..6f9863c9bd09 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java @@ -15,23 +15,46 @@ */ package com.android.server.location.timezone; +import static android.app.time.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO; import static android.app.time.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME; +import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DISABLED; +import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE; +import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED; import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME; +import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_DUMP_STATE; +import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_RECORD_PROVIDER_STATES; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND; +import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_START; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_STOP; -import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY; -import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY; -import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED; -import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED; + +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_UNKNOWN; import android.annotation.NonNull; +import android.app.time.GeolocationTimeZoneSuggestionProto; +import android.app.time.LocationTimeZoneManagerProto; +import android.app.time.LocationTimeZoneManagerServiceStateProto; +import android.app.time.TimeZoneProviderStateProto; import android.os.Bundle; import android.os.ShellCommand; +import android.util.IndentingPrintWriter; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.util.dump.DualDumpOutputStream; +import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; +import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; +import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; +import java.util.Objects; /** Implements the shell command interface for {@link LocationTimeZoneManagerService}. */ class LocationTimeZoneManagerShellCommand extends ShellCommand { @@ -58,9 +81,18 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { case SHELL_COMMAND_STOP: { return runStop(); } + case SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE: { + return runSetProviderModeOverride(); + } case SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND: { return runSendProviderTestCommand(); } + case SHELL_COMMAND_RECORD_PROVIDER_STATES: { + return runRecordProviderStates(); + } + case SHELL_COMMAND_DUMP_STATE: { + return runDumpControllerState(); + } default: { return handleDefaultCommands(cmd); } @@ -70,35 +102,42 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { @Override public void onHelp() { final PrintWriter pw = getOutPrintWriter(); - pw.println("Location Time Zone Manager (location_time_zone_manager) commands:"); + pw.println("Location Time Zone Manager (location_time_zone_manager) commands for tests:"); pw.println(" help"); pw.println(" Print this help text."); pw.printf(" %s\n", SHELL_COMMAND_START); pw.println(" Starts the location_time_zone_manager, creating time zone providers."); pw.printf(" %s\n", SHELL_COMMAND_STOP); pw.println(" Stops the location_time_zone_manager, destroying time zone providers."); + pw.printf(" %s <provider name> <mode>\n", SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE); + pw.println(" Sets a provider into a test mode next time the service started."); + pw.printf(" Values: %s|%s|%s\n", PROVIDER_MODE_OVERRIDE_NONE, + PROVIDER_MODE_OVERRIDE_DISABLED, PROVIDER_MODE_OVERRIDE_SIMULATED); + pw.printf(" %s (true|false)\n", SHELL_COMMAND_RECORD_PROVIDER_STATES); + pw.printf(" Enables / disables provider state recording mode. See also %s. The default" + + " state is always \"false\".\n", SHELL_COMMAND_DUMP_STATE); + pw.println(" Note: When enabled, this mode consumes memory and it is only intended for" + + " testing."); + pw.println(" It should be disabled after use, or the device can be rebooted to" + + " reset the mode to disabled."); + pw.println(" Disabling (or enabling repeatedly) clears any existing stored states."); + pw.printf(" %s [%s]\n", SHELL_COMMAND_DUMP_STATE, DUMP_STATE_OPTION_PROTO); + pw.println(" Dumps Location Time Zone Manager state for tests as text or binary proto" + + " form."); + pw.println(" See the LocationTimeZoneManagerServiceStateProto definition for details."); pw.printf(" %s <provider name> <test command>\n", SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND); pw.println(" Passes a test command to the named provider."); pw.println(); - pw.printf("%s details:\n", SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND); - pw.println(); pw.printf("<provider name> = One of %s\n", VALID_PROVIDER_NAMES); pw.println(); - pw.println("<test command> encoding:"); + pw.printf("%s details:\n", SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND); pw.println(); - TestCommand.printShellCommandEncodingHelp(pw); + pw.println("Provider <test command> encoding:"); pw.println(); - pw.printf("Provider modes can be modified by setting the \"%s\" or \"%s\"\n system" - + " property and restarting the service or rebooting the device.\n", - SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY, - SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY); - pw.println("Values are:"); - pw.printf(" %s - simulation mode (see below for commands)\n", - SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED); - pw.printf(" %s - disabled mode\n", SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED); + TestCommand.printShellCommandEncodingHelp(pw); pw.println(); - pw.println("Simulated providers can be used to test the system server behavior or to" + pw.println("Simulated provider mode can be used to test the system server behavior or to" + " reproduce bugs without the complexity of using real providers."); pw.println(); pw.println("The test commands for simulated providers are:"); @@ -132,6 +171,119 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { return 0; } + private int runSetProviderModeOverride() { + PrintWriter outPrintWriter = getOutPrintWriter(); + try { + String providerName = getNextArgRequired(); + String modeOverride = getNextArgRequired(); + outPrintWriter.println("Setting provider mode override for " + providerName + + " to " + modeOverride); + mService.setProviderModeOverride(providerName, modeOverride); + } catch (RuntimeException e) { + reportError(e); + return 1; + } + return 0; + } + + private int runRecordProviderStates() { + PrintWriter outPrintWriter = getOutPrintWriter(); + boolean enabled; + try { + String nextArg = getNextArgRequired(); + enabled = Boolean.parseBoolean(nextArg); + } catch (RuntimeException e) { + reportError(e); + return 1; + } + + outPrintWriter.println("Setting provider state recording to " + enabled); + try { + mService.setProviderStateRecordingEnabled(enabled); + } catch (IllegalStateException e) { + reportError(e); + return 2; + } + return 0; + } + + private int runDumpControllerState() { + LocationTimeZoneManagerServiceState state; + try { + state = mService.getStateForTests(); + } catch (RuntimeException e) { + reportError(e); + return 1; + } + + DualDumpOutputStream outputStream; + boolean useProto = Objects.equals(DUMP_STATE_OPTION_PROTO, getNextOption()); + if (useProto) { + FileDescriptor outFd = getOutFileDescriptor(); + outputStream = new DualDumpOutputStream(new ProtoOutputStream(outFd)); + } else { + outputStream = new DualDumpOutputStream( + new IndentingPrintWriter(getOutPrintWriter(), " ")); + } + if (state.getLastSuggestion() != null) { + GeolocationTimeZoneSuggestion lastSuggestion = state.getLastSuggestion(); + long lastSuggestionToken = outputStream.start( + "last_suggestion", LocationTimeZoneManagerServiceStateProto.LAST_SUGGESTION); + for (String zoneId : lastSuggestion.getZoneIds()) { + outputStream.write( + "zone_ids" , GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId); + } + for (String debugInfo : lastSuggestion.getDebugInfo()) { + outputStream.write( + "debug_info", GeolocationTimeZoneSuggestionProto.DEBUG_INFO, debugInfo); + } + outputStream.end(lastSuggestionToken); + } + + writeProviderStates(outputStream, state.getPrimaryProviderStates(), + "primary_provider_states", + LocationTimeZoneManagerServiceStateProto.PRIMARY_PROVIDER_STATES); + writeProviderStates(outputStream, state.getSecondaryProviderStates(), + "secondary_provider_states", + LocationTimeZoneManagerServiceStateProto.SECONDARY_PROVIDER_STATES); + outputStream.flush(); + + return 0; + } + + private static void writeProviderStates(DualDumpOutputStream outputStream, + List<LocationTimeZoneProvider.ProviderState> providerStates, String fieldName, + long fieldId) { + for (LocationTimeZoneProvider.ProviderState providerState : providerStates) { + long providerStateToken = outputStream.start(fieldName, fieldId); + outputStream.write("state", TimeZoneProviderStateProto.STATE, + convertProviderStateEnumToProtoEnum(providerState.stateEnum)); + outputStream.end(providerStateToken); + } + } + + private static int convertProviderStateEnumToProtoEnum(@ProviderStateEnum int stateEnum) { + switch (stateEnum) { + case PROVIDER_STATE_UNKNOWN: + return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_UNKNOWN; + case PROVIDER_STATE_STARTED_INITIALIZING: + return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_INITIALIZING; + case PROVIDER_STATE_STARTED_CERTAIN: + return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_CERTAIN; + case PROVIDER_STATE_STARTED_UNCERTAIN: + return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_UNCERTAIN; + case PROVIDER_STATE_STOPPED: + return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_DISABLED; + case PROVIDER_STATE_PERM_FAILED: + return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_PERM_FAILED; + case PROVIDER_STATE_DESTROYED: + return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_DESTROYED; + default: { + throw new IllegalArgumentException("Unknown stateEnum=" + stateEnum); + } + } + } + private int runSendProviderTestCommand() { PrintWriter outPrintWriter = getOutPrintWriter(); diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java index 132c1671f725..9a7b7750659c 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java @@ -49,6 +49,8 @@ import com.android.server.timezonedetector.Dumpable; import com.android.server.timezonedetector.ReferenceWithHistory; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -339,11 +341,20 @@ abstract class LocationTimeZoneProvider implements Dumpable { @NonNull final String mProviderName; /** + * Usually {@code false} but can be set to {@code true} for testing. + */ + @GuardedBy("mSharedLock") + private boolean mStateChangeRecording; + + @GuardedBy("mSharedLock") + @NonNull + private final ArrayList<ProviderState> mRecordedStates = new ArrayList<>(0); + + /** * The current state (with history for debugging). */ @GuardedBy("mSharedLock") - final ReferenceWithHistory<ProviderState> mCurrentState = - new ReferenceWithHistory<>(10); + final ReferenceWithHistory<ProviderState> mCurrentState = new ReferenceWithHistory<>(10); /** * Used for scheduling initialization timeouts, i.e. for providers that have just been started. @@ -423,6 +434,28 @@ abstract class LocationTimeZoneProvider implements Dumpable { abstract void onDestroy(); /** + * Sets the provider into state recording mode for tests. + */ + final void setStateChangeRecordingEnabled(boolean enabled) { + mThreadingDomain.assertCurrentThread(); + synchronized (mSharedLock) { + mStateChangeRecording = enabled; + mRecordedStates.clear(); + mRecordedStates.trimToSize(); + } + } + + /** + * Returns recorded states. + */ + final List<ProviderState> getRecordedStates() { + mThreadingDomain.assertCurrentThread(); + synchronized (mSharedLock) { + return new ArrayList<>(mRecordedStates); + } + } + + /** * Set the current state, for use by this class and subclasses only. If {@code #notifyChanges} * is {@code true} and {@code newState} is not equal to the old state, then {@link * ProviderListener#onProviderStateChange(ProviderState)} must be called on @@ -434,8 +467,11 @@ abstract class LocationTimeZoneProvider implements Dumpable { ProviderState oldState = mCurrentState.get(); mCurrentState.set(newState); onSetCurrentState(newState); - if (notifyChanges) { - if (!Objects.equals(newState, oldState)) { + if (!Objects.equals(newState, oldState)) { + if (mStateChangeRecording) { + mRecordedStates.add(newState); + } + if (notifyChanges) { mProviderListener.onProviderStateChange(newState); } } diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java index 69c57a9a5d74..103cdd997efc 100644 --- a/services/core/java/com/android/server/media/MediaShellCommand.java +++ b/services/core/java/com/android/server/media/MediaShellCommand.java @@ -67,7 +67,7 @@ public class MediaShellCommand extends ShellCommand { } if (sThread == null) { Looper.prepare(); - sThread = ActivityThread.systemMain(); + sThread = ActivityThread.currentActivityThread(); Context context = sThread.getSystemContext(); sMediaSessionManager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE); diff --git a/services/core/java/com/android/server/net/NetworkIdentitySet.java b/services/core/java/com/android/server/net/NetworkIdentitySet.java index 2326ad39fd27..bce80696f72c 100644 --- a/services/core/java/com/android/server/net/NetworkIdentitySet.java +++ b/services/core/java/com/android/server/net/NetworkIdentitySet.java @@ -20,8 +20,8 @@ import android.net.NetworkIdentity; import android.service.NetworkIdentitySetProto; import android.util.proto.ProtoOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; +import java.io.DataInput; +import java.io.DataOutput; import java.io.IOException; import java.util.HashSet; @@ -44,7 +44,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements public NetworkIdentitySet() { } - public NetworkIdentitySet(DataInputStream in) throws IOException { + public NetworkIdentitySet(DataInput in) throws IOException { final int version = in.readInt(); final int size = in.readInt(); for (int i = 0; i < size; i++) { @@ -89,7 +89,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements } } - public void writeToStream(DataOutputStream out) throws IOException { + public void writeToStream(DataOutput out) throws IOException { out.writeInt(VERSION_ADD_DEFAULT_NETWORK); out.writeInt(size()); for (NetworkIdentity ident : this) { @@ -143,7 +143,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements return true; } - private static void writeOptionalString(DataOutputStream out, String value) throws IOException { + private static void writeOptionalString(DataOutput out, String value) throws IOException { if (value != null) { out.writeByte(1); out.writeUTF(value); @@ -152,7 +152,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements } } - private static String readOptionalString(DataInputStream in) throws IOException { + private static String readOptionalString(DataInput in) throws IOException { if (in.readByte() != 0) { return in.readUTF(); } else { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index 407cedf38917..141fa6a17873 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 class NetworkPolicyManagerInternal { 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 0e7b4b8c9c5e..7c17356fa3cc 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -5352,7 +5352,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 +5451,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { && !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/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java index c4beddd42eaf..6aefe41891f9 100644 --- a/services/core/java/com/android/server/net/NetworkStatsCollection.java +++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java @@ -63,12 +63,15 @@ import com.google.android.collect.Lists; import com.google.android.collect.Maps; import java.io.BufferedInputStream; +import java.io.DataInput; import java.io.DataInputStream; +import java.io.DataOutput; import java.io.DataOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.net.ProtocolException; import java.time.ZonedDateTime; @@ -82,7 +85,7 @@ import java.util.Objects; * Collection of {@link NetworkStatsHistory}, stored based on combined key of * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself. */ -public class NetworkStatsCollection implements FileRotator.Reader { +public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer { /** File header magic number: "ANET" */ private static final int FILE_MAGIC = 0x414E4554; @@ -431,10 +434,10 @@ public class NetworkStatsCollection implements FileRotator.Reader { @Override public void read(InputStream in) throws IOException { - read(new DataInputStream(in)); + read((DataInput) new DataInputStream(in)); } - public void read(DataInputStream in) throws IOException { + private void read(DataInput in) throws IOException { // verify file magic header intact final int magic = in.readInt(); if (magic != FILE_MAGIC) { @@ -468,7 +471,13 @@ public class NetworkStatsCollection implements FileRotator.Reader { } } - public void write(DataOutputStream out) throws IOException { + @Override + public void write(OutputStream out) throws IOException { + write((DataOutput) new DataOutputStream(out)); + out.flush(); + } + + private void write(DataOutput out) throws IOException { // cluster key lists grouped by ident final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap(); for (Key key : mStats.keySet()) { @@ -497,8 +506,6 @@ public class NetworkStatsCollection implements FileRotator.Reader { history.writeToStream(out); } } - - out.flush(); } @Deprecated diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java index ce741693cb4b..978ae87d39d5 100644 --- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java @@ -42,7 +42,6 @@ import com.google.android.collect.Sets; import libcore.io.IoUtils; import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -375,7 +374,7 @@ public class NetworkStatsRecorder { @Override public void write(OutputStream out) throws IOException { - mCollection.write(new DataOutputStream(out)); + mCollection.write(out); mCollection.reset(); } } @@ -412,7 +411,7 @@ public class NetworkStatsRecorder { @Override public void write(OutputStream out) throws IOException { - mTemp.write(new DataOutputStream(out)); + mTemp.write(out); } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c10655896ec3..4ab827908e34 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -68,6 +68,7 @@ import static android.os.UserHandle.USER_NULL; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; +import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; @@ -445,16 +446,6 @@ public class NotificationManagerService extends SystemService { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) private static final long NOTIFICATION_TRAMPOLINE_BLOCK = 167676448L; - /** - * Rate limit showing toasts, on a per package basis. - * - * It limits the effects of {@link android.widget.Toast#show()} calls to prevent overburdening - * the user with too many toasts in a limited time. Any attempt to show more toasts than allowed - * in a certain time frame will result in the toast being discarded. - */ - @ChangeId - private static final long RATE_LIMIT_TOASTS = 154198299L; - private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; @@ -526,6 +517,9 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>(); final ArrayList<ToastRecord> mToastQueue = new ArrayList<>(); + // set of uids for which toast rate limiting is disabled + @GuardedBy("mToastQueue") + private final Set<Integer> mToastRateLimitingDisabledUids = new ArraySet<>(); final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>(); // True if the toast that's on top of the queue is being shown at the moment. @@ -863,7 +857,7 @@ public class NotificationManagerService extends SystemService { @VisibleForTesting protected void handleSavePolicyFile() { if (!IoThread.getHandler().hasCallbacks(mSavePolicyFile)) { - IoThread.getHandler().post(mSavePolicyFile); + IoThread.getHandler().postDelayed(mSavePolicyFile, 250); } } @@ -3067,6 +3061,22 @@ public class NotificationManagerService extends SystemService { } @Override + public void setToastRateLimitingEnabled(boolean enable) { + getContext().enforceCallingPermission( + android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING, + "App doesn't have the permission to enable/disable toast rate limiting"); + + synchronized (mToastQueue) { + int uid = Binder.getCallingUid(); + if (enable) { + mToastRateLimitingDisabledUids.remove(uid); + } else { + mToastRateLimitingDisabledUids.add(uid); + } + } + } + + @Override public void finishToken(String pkg, IBinder token) { synchronized (mToastQueue) { final long callingId = Binder.clearCallingIdentity(); @@ -7377,7 +7387,7 @@ public class NotificationManagerService extends SystemService { while (record != null) { int userId = UserHandle.getUserId(record.uid); boolean rateLimitingEnabled = - CompatChanges.isChangeEnabled(RATE_LIMIT_TOASTS, record.uid); + !mToastRateLimitingDisabledUids.contains(record.uid); boolean isWithinQuota = mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG); @@ -9497,7 +9507,7 @@ public class NotificationManagerService extends SystemService { public class NotificationListeners extends ManagedServices { static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners"; - static final String TAG_REQUESTED_LISTENERS = "requested_listeners"; + static final String TAG_REQUESTED_LISTENERS = "req_listeners"; static final String TAG_REQUESTED_LISTENER = "listener"; static final String ATT_COMPONENT = "component"; static final String ATT_TYPES = "types"; @@ -9686,7 +9696,7 @@ public class NotificationManagerService extends SystemService { final ComponentName cn = ComponentName.unflattenFromString( XmlUtils.readStringAttribute(parser, ATT_COMPONENT)); int approved = FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING - | FLAG_FILTER_TYPE_SILENT; + | FLAG_FILTER_TYPE_SILENT | FLAG_FILTER_TYPE_ONGOING; ArraySet<String> disallowedPkgs = new ArraySet<>(); final int listenerOuterDepth = parser.getDepth(); diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 094be0622bab..f8990c065341 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -167,6 +167,7 @@ public class AppsFilter implements Watchable, Snappable { * * @param observer The {@link Watcher} to be notified when the {@link Watchable} changes. */ + @Override public void registerObserver(@NonNull Watcher observer) { mWatchable.registerObserver(observer); } @@ -177,17 +178,29 @@ public class AppsFilter implements Watchable, Snappable { * * @param observer The {@link Watcher} that should not be in the notification list. */ + @Override public void unregisterObserver(@NonNull Watcher observer) { mWatchable.unregisterObserver(observer); } /** + * Return true if the {@link Watcher) is a registered observer. + * @param observer A {@link Watcher} that might be registered + * @return true if the observer is registered with this {@link Watchable}. + */ + @Override + public boolean isRegisteredObserver(@NonNull Watcher observer) { + return mWatchable.isRegisteredObserver(observer); + } + + /** * Invokes {@link Watcher#onChange} on each registered observer. The method can be called * with the {@link Watchable} that generated the event. In a tree of {@link Watchable}s, this * is generally the first (deepest) {@link Watchable} to detect a change. * * @param what The {@link Watchable} that generated the event. */ + @Override public void dispatchChange(@Nullable Watchable what) { mSnapshot = null; mWatchable.dispatchChange(what); @@ -443,7 +456,7 @@ public class AppsFilter implements Watchable, Snappable { } final StateProvider stateProvider = command -> { synchronized (injector.getLock()) { - command.currentState(injector.getSettings().getPackagesLocked().untrackedMap(), + command.currentState(injector.getSettings().getPackagesLocked().untrackedStorage(), injector.getUserManagerInternal().getUserInfos()); } }; @@ -979,7 +992,7 @@ public class AppsFilter implements Watchable, Snappable { @Nullable SparseArray<int[]> getVisibilityAllowList(PackageSetting setting, int[] users, WatchedArrayMap<String, PackageSetting> existingSettings) { - return getVisibilityAllowList(setting, users, existingSettings.untrackedMap()); + return getVisibilityAllowList(setting, users, existingSettings.untrackedStorage()); } /** diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java index bf7f466457e9..aae6ce46de66 100644 --- a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java +++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java @@ -19,8 +19,8 @@ package com.android.server.pm; import android.annotation.NonNull; import android.content.IntentFilter; +import com.android.server.WatchableIntentResolver; import com.android.server.utils.Snappable; -import com.android.server.utils.WatchableIntentResolver; import java.util.List; @@ -57,7 +57,7 @@ class CrossProfileIntentResolver */ public CrossProfileIntentResolver snapshot() { CrossProfileIntentResolver result = new CrossProfileIntentResolver(); - result.doCopy(this); + result.copyFrom(this); return result; } } diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java index 520871ff40c8..4f986bd5276b 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 final class DumpState { 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/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java index 69d3e5c2f941..c3bca285dca3 100644 --- a/services/core/java/com/android/server/pm/InstantAppRegistry.java +++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java @@ -154,6 +154,9 @@ class InstantAppRegistry implements Watchable, Snappable { public void unregisterObserver(@NonNull Watcher observer) { mWatchable.unregisterObserver(observer); } + public boolean isRegisteredObserver(@NonNull Watcher observer) { + return mWatchable.isRegisteredObserver(observer); + } public void dispatchChange(@Nullable Watchable what) { mSnapshot = null; mWatchable.dispatchChange(what); 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 000000000000..48f6bf84c28b --- /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 + +# TODO: include /MULTIUSER_OWNERS + +# Enterprise owners +rubinxu@google.com +sandness@google.com diff --git a/services/core/java/com/android/server/pm/ModuleInfoProvider.java b/services/core/java/com/android/server/pm/ModuleInfoProvider.java index 06706cd06e11..0ffc1ed9e90c 100644 --- a/services/core/java/com/android/server/pm/ModuleInfoProvider.java +++ b/services/core/java/com/android/server/pm/ModuleInfoProvider.java @@ -184,7 +184,7 @@ public class ModuleInfoProvider { List<PackageInfo> allPackages; try { allPackages = mPackageManager.getInstalledPackages( - flags | PackageManager.MATCH_APEX, UserHandle.USER_SYSTEM).getList(); + flags | PackageManager.MATCH_APEX, UserHandle.getCallingUserId()).getList(); } catch (RemoteException e) { Slog.w(TAG, "Unable to retrieve all package names", e); return Collections.emptyList(); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index f97a5ee45346..3d04b5607922 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.UserHandle; 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { }; 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 cf9867cf312a..4467b5110d08 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.incremental.IncrementalManager.isIncrementalPath; 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.UserManager; 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; @@ -393,7 +395,11 @@ import com.android.server.security.VerityUtils; import com.android.server.storage.DeviceStorageMonitorInternal; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.utils.TimingsTraceAndSlog; +import com.android.server.utils.Watchable; +import com.android.server.utils.Watched; import com.android.server.utils.WatchedArrayMap; +import com.android.server.utils.WatchedSparseBooleanArray; +import com.android.server.utils.Watcher; import com.android.server.wm.ActivityTaskManagerInternal; import dalvik.system.CloseGuard; @@ -506,6 +512,9 @@ public class PackageManagerService extends IPackageManager.Stub public static final boolean DEBUG_PERMISSIONS = false; private static final boolean DEBUG_SHARED_LIBRARIES = false; 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 @@ -641,6 +650,24 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ -793,11 +820,13 @@ public class PackageManagerService extends IPackageManager.Stub final Object mLock; // Keys are String (package name), values are Package. + @Watched @GuardedBy("mLock") - final ArrayMap<String, AndroidPackage> mPackages = new ArrayMap<>(); + final WatchedArrayMap<String, AndroidPackage> mPackages = new WatchedArrayMap<>(); // Keys are isolated uids and values are the uid of the application - // that created the isolated proccess. + // that created the isolated process. + @Watched @GuardedBy("mLock") final SparseIntArray mIsolatedOwners = new SparseIntArray(); @@ -822,6 +851,7 @@ public class PackageManagerService extends IPackageManager.Stub private final TestUtilityService mTestUtilityService; + @Watched @GuardedBy("mLock") final Settings mSettings; @@ -852,6 +882,7 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mAvailableFeatures") final ArrayMap<String, FeatureInfo> mAvailableFeatures; + @Watched private final InstantAppRegistry mInstantAppRegistry; @GuardedBy("mLock") @@ -899,6 +930,11 @@ public class PackageManagerService extends IPackageManager.Stub 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. * @@ -1208,12 +1244,14 @@ public class PackageManagerService extends IPackageManager.Stub // Avoid invalidation-thrashing by preventing cache invalidations from causing property // writes if the cache isn't enabled yet. We re-enable writes later when we're // done initializing. + sSnapshotCorked = true; PackageManager.corkPackageInfoCache(); } @Override public void enablePackageCaches() { // Uncork cache invalidations and allow clients to cache package information. + sSnapshotCorked = false; PackageManager.uncorkPackageInfoCache(); } } @@ -1286,18 +1324,23 @@ public class PackageManagerService extends IPackageManager.Stub public String incrementalVersion = Build.VERSION.INCREMENTAL; } + @Watched private final AppsFilter mAppsFilter; final PackageParser2.Callback mPackageParserCallback; // Currently known shared libraries. - final ArrayMap<String, LongSparseArray<SharedLibraryInfo>> mSharedLibraries = new ArrayMap<>(); - final ArrayMap<String, LongSparseArray<SharedLibraryInfo>> mStaticLibsByDeclaringPackage = - new ArrayMap<>(); + @Watched + final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> mSharedLibraries = + new WatchedArrayMap<>(); + @Watched + final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> + mStaticLibsByDeclaringPackage = new WatchedArrayMap<>(); // Mapping from instrumentation class names to info about them. - final ArrayMap<ComponentName, ParsedInstrumentation> mInstrumentation = - new ArrayMap<>(); + @Watched + final WatchedArrayMap<ComponentName, ParsedInstrumentation> mInstrumentation = + new WatchedArrayMap<>(); // Packages whose data we have transfered into another package, thus // should no longer exist. @@ -1336,15 +1379,22 @@ public class PackageManagerService extends IPackageManager.Stub /** Token for keys in mPendingEnableRollback. */ private int mPendingEnableRollbackToken = 0; + @Watched volatile boolean mSystemReady; - volatile boolean mSafeMode; + @Watched + private volatile boolean mSafeMode; volatile boolean mHasSystemUidErrors; - private volatile SparseBooleanArray mWebInstantAppsDisabled = new SparseBooleanArray(); + @Watched + private final WatchedSparseBooleanArray mWebInstantAppsDisabled = + new WatchedSparseBooleanArray(); - ApplicationInfo mAndroidApplication; + @Watched + private ApplicationInfo mAndroidApplication; + @Watched final ActivityInfo mResolveActivity = new ActivityInfo(); final ResolveInfo mResolveInfo = new ResolveInfo(); - ComponentName mResolveComponentName; + @Watched + private ComponentName mResolveComponentName; AndroidPackage mPlatformPackage; ComponentName mCustomResolverComponentName; @@ -1361,8 +1411,10 @@ public class PackageManagerService extends IPackageManager.Stub final ComponentName mInstantAppResolverSettingsComponent; /** Activity used to install instant applications */ - ActivityInfo mInstantAppInstallerActivity; - final ResolveInfo mInstantAppInstallerInfo = new ResolveInfo(); + @Watched + private ActivityInfo mInstantAppInstallerActivity; + @Watched + private final ResolveInfo mInstantAppInstallerInfo = new ResolveInfo(); private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>> mNoKillInstallObservers = Collections.synchronizedMap(new HashMap<>()); @@ -1744,7 +1796,7 @@ public class PackageManagerService extends IPackageManager.Stub private static final long DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD = 2 * 60 * 60 * 1000L; /* two hours */ - UserManagerService mUserManager; + final UserManagerService mUserManager; // Stores a list of users whose package restrictions file needs to be updated private ArraySet<Integer> mDirtyUsers = new ArraySet<>(); @@ -1828,6 +1880,2908 @@ public class PackageManagerService extends IPackageManager.Stub */ public static void invalidatePackageInfoCache() { PackageManager.invalidatePackageInfoCache(); + onChanged(); + } + + private final Watcher mWatcher = new Watcher() { + @Override + public void onChange(@Nullable Watchable what) { + PackageManagerService.this.onChange(what); + } + }; + + /** + * A Snapshot is a subset of PackageManagerService state. A snapshot is either live + * or snapped. Live snapshots directly reference PackageManagerService attributes. + * Snapped snapshots contain deep copies of the attributes. + */ + private class Snapshot { + public static final int LIVE = 1; + public static final int SNAPPED = 2; + + public final Settings settings; + public final SparseIntArray isolatedOwners; + public final WatchedArrayMap<String, AndroidPackage> packages; + public final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> sharedLibs; + public final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> staticLibs; + public final WatchedArrayMap<ComponentName, ParsedInstrumentation> instrumentation; + public final WatchedSparseBooleanArray webInstantAppsDisabled; + public final ComponentName resolveComponentName; + public final ActivityInfo resolveActivity; + public final ActivityInfo instantAppInstallerActivity; + public final ResolveInfo instantAppInstallerInfo; + public final InstantAppRegistry instantAppRegistry; + public final ApplicationInfo androidApplication; + public final String appPredictionServicePackage; + public final AppsFilter appsFilter; + public final PackageManagerService service; + + Snapshot(int type) { + if (type == Snapshot.SNAPPED) { + settings = mSettings.snapshot(); + isolatedOwners = mIsolatedOwners.clone(); + packages = mPackages.snapshot(); + sharedLibs = mSharedLibraries.snapshot(); + staticLibs = mStaticLibsByDeclaringPackage.snapshot(); + instrumentation = mInstrumentation.snapshot(); + resolveComponentName = mResolveComponentName.clone(); + resolveActivity = new ActivityInfo(mResolveActivity); + instantAppInstallerActivity = + (mInstantAppInstallerActivity == null) + ? null + : new ActivityInfo(mInstantAppInstallerActivity); + instantAppInstallerInfo = new ResolveInfo(mInstantAppInstallerInfo); + webInstantAppsDisabled = mWebInstantAppsDisabled.snapshot(); + instantAppRegistry = mInstantAppRegistry.snapshot(); + androidApplication = + (mAndroidApplication == null) + ? null + : new ApplicationInfo(mAndroidApplication); + appPredictionServicePackage = mAppPredictionServicePackage; + appsFilter = mAppsFilter.snapshot(); + } else if (type == Snapshot.LIVE) { + settings = mSettings; + isolatedOwners = mIsolatedOwners; + packages = mPackages; + sharedLibs = mSharedLibraries; + staticLibs = mStaticLibsByDeclaringPackage; + instrumentation = mInstrumentation; + resolveComponentName = mResolveComponentName; + resolveActivity = mResolveActivity; + instantAppInstallerActivity = mInstantAppInstallerActivity; + instantAppInstallerInfo = mInstantAppInstallerInfo; + webInstantAppsDisabled = mWebInstantAppsDisabled; + instantAppRegistry = mInstantAppRegistry; + androidApplication = mAndroidApplication; + appPredictionServicePackage = mAppPredictionServicePackage; + appsFilter = mAppsFilter; + } else { + throw new IllegalArgumentException(); + } + service = PackageManagerService.this; + } + } + + /** + * A computer provides the functional interface to the cache + */ + private interface Computer { + + @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, + int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid, + int userId, boolean resolveForStart, boolean allowDynamicSplits); + @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, + int flags, int userId); + @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent, String resolvedType, + int flags, int userId, int callingUid, boolean includeInstantApps); + @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent, + String resolvedType, int flags, int userId, int callingUid, + String instantAppPkgName); + @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(Intent intent, + String resolvedType, int flags, int filterCallingUid, int userId, + boolean resolveForStart, boolean allowDynamicSplits, String pkgName, + String instantAppPkgName); + @Nullable ComponentName findInstallFailureActivity(String packageName, int filterCallingUid, + int userId); + ActivityInfo getActivityInfo(ComponentName component, int flags, int userId); + ActivityInfo getActivityInfoInternal(ComponentName component, int flags, + int filterCallingUid, int userId); + ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags, + int filterCallingUid, int userId); + AndroidPackage getPackage(String packageName); + AndroidPackage getPackage(int uid); + ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags, + int filterCallingUid, int userId); + ApplicationInfo getApplicationInfo(String packageName, int flags, int userId); + ApplicationInfo getApplicationInfoInternal(String packageName, int flags, + int filterCallingUid, int userId); + ApplicationInfo getApplicationInfoInternalBody(String packageName, int flags, + int filterCallingUid, int userId); + ArrayList<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPrBody(Intent intent, + int matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo, + int userId, boolean debug); + ComponentName getDefaultHomeActivity(int userId); + ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates, int userId); + CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent, String resolvedType, + int flags, int sourceUserId, int parentUserId); + Intent getHomeIntent(); + List<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(Intent intent, + String resolvedType, int userId); + List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos, + String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid, + boolean resolveForStart, int userId, Intent intent); + List<ResolveInfo> applyPostServiceResolutionFilter(List<ResolveInfo> resolveInfos, + String instantAppPkgName, @UserIdInt int userId, int filterCallingUid); + List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent, + int matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo, + int userId); + List<ResolveInfo> filterIfNotSystemUser(List<ResolveInfo> resolveInfos, int userId); + List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result, Intent intent, + String resolvedType, int flags, int userId, boolean resolveForStart, + boolean isRequesterInstantApp); + PackageInfo generatePackageInfo(PackageSetting ps, int flags, int userId); + PackageInfo getPackageInfo(String packageName, int flags, int userId); + PackageInfo getPackageInfoInternal(String packageName, long versionCode, int flags, + int filterCallingUid, int userId); + PackageInfo getPackageInfoInternalBody(String packageName, long versionCode, int flags, + int filterCallingUid, int userId); + PackageSetting getPackageSetting(String packageName); + PackageSetting getPackageSettingInternal(String packageName, int callingUid); + ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId); + ParceledListSlice<PackageInfo> getInstalledPackagesBody(int flags, int userId, + int callingUid); + ResolveInfo createForwardingResolveInfo(CrossProfileIntentFilter filter, Intent intent, + String resolvedType, int flags, int sourceUserId); + ResolveInfo createForwardingResolveInfoUnchecked(IntentFilter filter, int sourceUserId, + int targetUserId); + ResolveInfo queryCrossProfileIntents(List<CrossProfileIntentFilter> matchingFilters, + Intent intent, String resolvedType, int flags, int sourceUserId, + boolean matchInCurrentProfile); + ResolveInfo querySkipCurrentProfileIntents(List<CrossProfileIntentFilter> matchingFilters, + Intent intent, String resolvedType, int flags, int sourceUserId); + ServiceInfo getServiceInfo(ComponentName component, int flags, int userId); + ServiceInfo getServiceInfoBody(ComponentName component, int flags, int userId, + int callingUid); + SharedLibraryInfo getSharedLibraryInfoLPr(String name, long version); + String getInstantAppPackageName(int callingUid); + String resolveExternalPackageNameLPr(AndroidPackage pkg); + String resolveInternalPackageNameInternalLocked(String packageName, long versionCode, + int callingUid); + String resolveInternalPackageNameLPr(String packageName, long versionCode); + String[] getPackagesForUid(int uid); + String[] getPackagesForUidInternal(int uid, int callingUid); + String[] getPackagesForUidInternalBody(int callingUid, int userId, int appId, + boolean isCallerInstantApp); + UserInfo getProfileParent(int userId); + boolean areWebInstantAppsDisabled(int userId); + boolean canViewInstantApps(int callingUid, int userId); + boolean filterSharedLibPackageLPr(@Nullable PackageSetting ps, int uid, int userId, + int flags); + boolean hasCrossUserPermission(int callingUid, int callingUserId, int userId, + boolean requireFullPermission, boolean requirePermissionWhenSameUser); + boolean hasNonNegativePriority(List<ResolveInfo> resolveInfos); + boolean hasPermission(String permission); + boolean isCallerSameApp(String packageName, int uid); + boolean isComponentVisibleToInstantApp(@Nullable ComponentName component); + boolean isComponentVisibleToInstantApp(@Nullable ComponentName component, + @ComponentType int type); + boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId, + String resolvedType, int flags); + boolean isInstantApp(String packageName, int userId); + boolean isInstantAppInternal(String packageName, @UserIdInt int userId, int callingUid); + boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId, int callingUid); + boolean isInstantAppResolutionAllowed(Intent intent, List<ResolveInfo> resolvedActivities, + int userId, boolean skipPackageCheck); + boolean isInstantAppResolutionAllowedBody(Intent intent, + List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck); + boolean isPersistentPreferredActivitySetByDpm(Intent intent, int userId, + String resolvedType, int flags); + boolean isRecentsAccessingChildProfiles(int callingUid, int targetUserId); + boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId); + boolean isUserEnabled(int userId); + boolean shouldFilterApplicationLocked(@Nullable PackageSetting ps, int callingUid, + @Nullable ComponentName component, @ComponentType int componentType, int userId); + boolean shouldFilterApplicationLocked(@Nullable PackageSetting ps, int callingUid, + int userId); + int bestDomainVerificationStatus(int status1, int status2); + int checkUidPermission(String permName, int uid); + int getPackageUidInternal(String packageName, int flags, int userId, int callingUid); + int updateFlags(int flags, int userId); + int updateFlagsForApplication(int flags, int userId); + int updateFlagsForComponent(int flags, int userId); + int updateFlagsForPackage(int flags, int userId); + int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, + boolean isImplicitImageCaptureIntentAndNotSetByDpc); + int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, + boolean onlyExposedExplicitly, boolean isImplicitImageCaptureIntentAndNotSetByDpc); + long getDomainVerificationStatusLPr(PackageSetting ps, int userId); + void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId, + boolean requireFullPermission, boolean checkShell, String message); + void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, + boolean requireFullPermission, boolean checkShell, String message); + void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, + boolean requireFullPermission, boolean checkShell, + boolean requirePermissionWhenSameUser, String message); + } + + /** + * This class contains the implementation of the Computer functions. It + * is entirely self-contained - it has no implicit access to + * PackageManagerService. + */ + private static class ComputerEngine implements Computer { + + // Cached attributes. The names in this class are the same as the + // names in PackageManagerService; see that class for documentation. + private final Settings mSettings; + private final SparseIntArray mIsolatedOwners; + private final WatchedArrayMap<String, AndroidPackage> mPackages; + private final WatchedArrayMap<ComponentName, ParsedInstrumentation> + mInstrumentation; + private final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> + mStaticLibsByDeclaringPackage; + private final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> + mSharedLibraries; + private final ComponentName mLocalResolveComponentName; + private final ActivityInfo mResolveActivity; + private final WatchedSparseBooleanArray mWebInstantAppsDisabled; + private final ActivityInfo mLocalInstantAppInstallerActivity; + private final ResolveInfo mInstantAppInstallerInfo; + private final InstantAppRegistry mInstantAppRegistry; + private final ApplicationInfo mLocalAndroidApplication; + + // Immutable service attribute + private final String mAppPredictionServicePackage; + + // TODO: create cache copies of the following attributes + private final AppsFilter mAppsFilter; + + // The following are not cloned since changes to these have never + // been guarded by the PMS lock. + private final Context mContext; + private final UserManagerService mUserManager; + private final PermissionManagerServiceInternal mPermissionManager; + private final ApexManager mApexManager; + private final Injector mInjector; + private final ComponentResolver mComponentResolver; + private final InstantAppResolverConnection mInstantAppResolverConnection; + private final DefaultAppProvider mDefaultAppProvider; + + // PackageManagerService attributes that are primitives are referenced through the + // pms object directly. Primitives are the only attributes so referenced. + protected final PackageManagerService mService; + protected boolean safeMode() { + return mService.mSafeMode; + } + protected ComponentName resolveComponentName() { + return mLocalResolveComponentName; + } + protected ActivityInfo instantAppInstallerActivity() { + return mLocalInstantAppInstallerActivity; + } + protected ApplicationInfo androidApplication() { + return mLocalAndroidApplication; + } + + ComputerEngine(Snapshot args) { + mSettings = args.settings; + mIsolatedOwners = args.isolatedOwners; + mPackages = args.packages; + mSharedLibraries = args.sharedLibs; + mStaticLibsByDeclaringPackage = args.staticLibs; + mInstrumentation = args.instrumentation; + mWebInstantAppsDisabled = args.webInstantAppsDisabled; + mLocalResolveComponentName = args.resolveComponentName; + mResolveActivity = args.resolveActivity; + mLocalInstantAppInstallerActivity = args.instantAppInstallerActivity; + mInstantAppInstallerInfo = args.instantAppInstallerInfo; + mInstantAppRegistry = args.instantAppRegistry; + mLocalAndroidApplication = args.androidApplication; + mAppsFilter = args.appsFilter; + + mAppPredictionServicePackage = args.appPredictionServicePackage; + + // The following are not cached copies. Instead they are + // references to outside services. + mPermissionManager = args.service.mPermissionManager; + mUserManager = args.service.mUserManager; + mContext = args.service.mContext; + mInjector = args.service.mInjector; + mApexManager = args.service.mApexManager; + mComponentResolver = args.service.mComponentResolver; + mInstantAppResolverConnection = args.service.mInstantAppResolverConnection; + mDefaultAppProvider = args.service.mDefaultAppProvider; + + // Used to reference PMS attributes that are primitives and which are not + // updated under control of the PMS lock. + mService = args.service; + } + + public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, + String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags, + int filterCallingUid, int userId, boolean resolveForStart, + boolean allowDynamicSplits) { + if (!mUserManager.exists(userId)) return Collections.emptyList(); + final String instantAppPkgName = getInstantAppPackageName(filterCallingUid); + enforceCrossUserPermission(Binder.getCallingUid(), userId, + false /* requireFullPermission */, false /* checkShell */, + "query intent activities"); + final String pkgName = intent.getPackage(); + ComponentName comp = intent.getComponent(); + if (comp == null) { + if (intent.getSelector() != null) { + intent = intent.getSelector(); + comp = intent.getComponent(); + } + } + + flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart, + comp != null || pkgName != null /*onlyExposedExplicitly*/, + isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType, + flags)); + if (comp != null) { + final List<ResolveInfo> list = new ArrayList<>(1); + final ActivityInfo ai = getActivityInfo(comp, flags, userId); + if (ai != null) { + // When specifying an explicit component, we prevent the activity from being + // used when either 1) the calling package is normal and the activity is within + // an ephemeral application or 2) the calling package is ephemeral and the + // activity is not visible to ephemeral applications. + final boolean matchInstantApp = + (flags & PackageManager.MATCH_INSTANT) != 0; + final boolean matchVisibleToInstantAppOnly = + (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0; + final boolean matchExplicitlyVisibleOnly = + (flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0; + final boolean isCallerInstantApp = + instantAppPkgName != null; + final boolean isTargetSameInstantApp = + comp.getPackageName().equals(instantAppPkgName); + final boolean isTargetInstantApp = + (ai.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0; + final boolean isTargetVisibleToInstantApp = + (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0; + final boolean isTargetExplicitlyVisibleToInstantApp = + isTargetVisibleToInstantApp + && (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) + == 0; + final boolean isTargetHiddenFromInstantApp = + !isTargetVisibleToInstantApp + || (matchExplicitlyVisibleOnly + && !isTargetExplicitlyVisibleToInstantApp); + final boolean blockInstantResolution = + !isTargetSameInstantApp + && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp) + || (matchVisibleToInstantAppOnly && isCallerInstantApp + && isTargetHiddenFromInstantApp)); + final boolean blockNormalResolution = + !resolveForStart && !isTargetInstantApp && !isCallerInstantApp + && shouldFilterApplicationLocked( + getPackageSettingInternal(ai.applicationInfo.packageName, + Process.SYSTEM_UID), filterCallingUid, userId); + if (!blockInstantResolution && !blockNormalResolution) { + final ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = ai; + list.add(ri); + } + } + + List<ResolveInfo> result = applyPostResolutionFilter( + list, instantAppPkgName, allowDynamicSplits, filterCallingUid, + resolveForStart, + userId, intent); + return result; + } + + QueryIntentActivitiesResult lockedResult = + queryIntentActivitiesInternalBody( + intent, resolvedType, flags, filterCallingUid, userId, resolveForStart, + allowDynamicSplits, pkgName, instantAppPkgName); + if (lockedResult.answer != null) { + return lockedResult.answer; + } + + if (lockedResult.addInstant) { + String callingPkgName = getInstantAppPackageName(filterCallingUid); + boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId); + lockedResult.result = maybeAddInstantAppInstaller(lockedResult.result, intent, + resolvedType, flags, userId, resolveForStart, isRequesterInstantApp); + } + if (lockedResult.sortResult) { + Collections.sort(lockedResult.result, RESOLVE_PRIORITY_SORTER); + } + return applyPostResolutionFilter( + lockedResult.result, instantAppPkgName, allowDynamicSplits, filterCallingUid, + resolveForStart, userId, intent); + } + + public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, + String resolvedType, int flags, int userId) { + return queryIntentActivitiesInternal( + intent, resolvedType, flags, 0 /*privateResolveFlags*/, Binder.getCallingUid(), + userId, false /*resolveForStart*/, true /*allowDynamicSplits*/); + } + + public @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent, + String resolvedType, int flags, int userId, int callingUid, + boolean includeInstantApps) { + if (!mUserManager.exists(userId)) return Collections.emptyList(); + enforceCrossUserOrProfilePermission(callingUid, + userId, + false /*requireFullPermission*/, + false /*checkShell*/, + "query intent receivers"); + final String instantAppPkgName = getInstantAppPackageName(callingUid); + flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps, + false /* isImplicitImageCaptureIntentAndNotSetByDpc */); + ComponentName comp = intent.getComponent(); + if (comp == null) { + if (intent.getSelector() != null) { + intent = intent.getSelector(); + comp = intent.getComponent(); + } + } + if (comp != null) { + final List<ResolveInfo> list = new ArrayList<>(1); + final ServiceInfo si = getServiceInfo(comp, flags, userId); + if (si != null) { + // When specifying an explicit component, we prevent the service from being + // used when either 1) the service is in an instant application and the + // caller is not the same instant application or 2) the calling package is + // ephemeral and the activity is not visible to ephemeral applications. + final boolean matchInstantApp = + (flags & PackageManager.MATCH_INSTANT) != 0; + final boolean matchVisibleToInstantAppOnly = + (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0; + final boolean isCallerInstantApp = + instantAppPkgName != null; + final boolean isTargetSameInstantApp = + comp.getPackageName().equals(instantAppPkgName); + final boolean isTargetInstantApp = + (si.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0; + final boolean isTargetHiddenFromInstantApp = + (si.flags & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0; + final boolean blockInstantResolution = + !isTargetSameInstantApp + && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp) + || (matchVisibleToInstantAppOnly && isCallerInstantApp + && isTargetHiddenFromInstantApp)); + + final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp + && shouldFilterApplicationLocked( + getPackageSettingInternal(si.applicationInfo.packageName, + Process.SYSTEM_UID), callingUid, userId); + if (!blockInstantResolution && !blockNormalResolution) { + final ResolveInfo ri = new ResolveInfo(); + ri.serviceInfo = si; + list.add(ri); + } + } + return list; + } + + return queryIntentServicesInternalBody(intent, resolvedType, flags, + userId, callingUid, instantAppPkgName); + } + + public @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent, + String resolvedType, int flags, int userId, int callingUid, + String instantAppPkgName) { + // reader + String pkgName = intent.getPackage(); + if (pkgName == null) { + final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(intent, + resolvedType, flags, userId); + if (resolveInfos == null) { + return Collections.emptyList(); + } + return applyPostServiceResolutionFilter( + resolveInfos, instantAppPkgName, userId, callingUid); + } + final AndroidPackage pkg = mPackages.get(pkgName); + if (pkg != null) { + final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(intent, + resolvedType, flags, pkg.getServices(), + userId); + if (resolveInfos == null) { + return Collections.emptyList(); + } + return applyPostServiceResolutionFilter( + resolveInfos, instantAppPkgName, userId, callingUid); + } + return Collections.emptyList(); + } + + public @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody( + Intent intent, String resolvedType, int flags, int filterCallingUid, int userId, + boolean resolveForStart, boolean allowDynamicSplits, String pkgName, + String instantAppPkgName) { + // reader + boolean sortResult = false; + boolean addInstant = false; + List<ResolveInfo> result = null; + if (pkgName == null) { + List<CrossProfileIntentFilter> matchingFilters = + getMatchingCrossProfileIntentFilters(intent, resolvedType, userId); + // Check for results that need to skip the current profile. + ResolveInfo xpResolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent, + resolvedType, flags, userId); + if (xpResolveInfo != null) { + List<ResolveInfo> xpResult = new ArrayList<>(1); + xpResult.add(xpResolveInfo); + return new QueryIntentActivitiesResult( + applyPostResolutionFilter( + filterIfNotSystemUser(xpResult, userId), instantAppPkgName, + allowDynamicSplits, filterCallingUid, resolveForStart, userId, + intent)); + } + + // Check for results in the current profile. + result = filterIfNotSystemUser(mComponentResolver.queryActivities( + intent, resolvedType, flags, userId), userId); + addInstant = isInstantAppResolutionAllowed(intent, result, userId, + false /*skipPackageCheck*/); + // Check for cross profile results. + boolean hasNonNegativePriorityResult = hasNonNegativePriority(result); + xpResolveInfo = queryCrossProfileIntents( + matchingFilters, intent, resolvedType, flags, userId, + hasNonNegativePriorityResult); + if (xpResolveInfo != null && isUserEnabled(xpResolveInfo.targetUserId)) { + boolean isVisibleToUser = filterIfNotSystemUser( + Collections.singletonList(xpResolveInfo), userId).size() > 0; + if (isVisibleToUser) { + result.add(xpResolveInfo); + sortResult = true; + } + } + if (intent.hasWebURI()) { + CrossProfileDomainInfo xpDomainInfo = null; + final UserInfo parent = getProfileParent(userId); + if (parent != null) { + xpDomainInfo = getCrossProfileDomainPreferredLpr(intent, resolvedType, + flags, userId, parent.id); + } + if (xpDomainInfo != null) { + if (xpResolveInfo != null) { + // If we didn't remove it, the cross-profile ResolveInfo would be twice + // in the result. + result.remove(xpResolveInfo); + } + if (result.size() == 0 && !addInstant) { + // No result in current profile, but found candidate in parent user. + // And we are not going to add ephemeral app, so we can return the + // result straight away. + result.add(xpDomainInfo.resolveInfo); + return new QueryIntentActivitiesResult( + applyPostResolutionFilter(result, instantAppPkgName, + allowDynamicSplits, filterCallingUid, resolveForStart, + userId, intent)); + } + } else if (result.size() <= 1 && !addInstant) { + // No result in parent user and <= 1 result in current profile, and we + // are not going to add ephemeral app, so we can return the result without + // further processing. + return new QueryIntentActivitiesResult( + applyPostResolutionFilter(result, instantAppPkgName, + allowDynamicSplits, filterCallingUid, resolveForStart, userId, + intent)); + } + // We have more than one candidate (combining results from current and parent + // profile), so we need filtering and sorting. + result = filterCandidatesWithDomainPreferredActivitiesLPr( + intent, flags, result, xpDomainInfo, userId); + sortResult = true; + } + } else { + final PackageSetting setting = + getPackageSettingInternal(pkgName, Process.SYSTEM_UID); + result = null; + if (setting != null && setting.pkg != null && (resolveForStart + || !shouldFilterApplicationLocked(setting, filterCallingUid, userId))) { + result = filterIfNotSystemUser(mComponentResolver.queryActivities( + intent, resolvedType, flags, setting.pkg.getActivities(), userId), + userId); + } + if (result == null || result.size() == 0) { + // the caller wants to resolve for a particular package; however, there + // were no installed results, so, try to find an ephemeral result + addInstant = isInstantAppResolutionAllowed( + intent, null /*result*/, userId, true /*skipPackageCheck*/); + if (result == null) { + result = new ArrayList<>(); + } + } + } + return new QueryIntentActivitiesResult(sortResult, addInstant, result); + } + + /** + * Returns the activity component that can handle install failures. + * <p>By default, the instant application installer handles failures. However, an + * application may want to handle failures on its own. Applications do this by + * creating an activity with an intent filter that handles the action + * {@link Intent#ACTION_INSTALL_FAILURE}. + */ + public @Nullable ComponentName findInstallFailureActivity( + String packageName, int filterCallingUid, int userId) { + final Intent failureActivityIntent = new Intent(Intent.ACTION_INSTALL_FAILURE); + failureActivityIntent.setPackage(packageName); + // IMPORTANT: disallow dynamic splits to avoid an infinite loop + final List<ResolveInfo> result = queryIntentActivitiesInternal( + failureActivityIntent, null /*resolvedType*/, 0 /*flags*/, + 0 /*privateResolveFlags*/, filterCallingUid, userId, false /*resolveForStart*/, + false /*allowDynamicSplits*/); + final int NR = result.size(); + if (NR > 0) { + for (int i = 0; i < NR; i++) { + final ResolveInfo info = result.get(i); + if (info.activityInfo.splitName != null) { + continue; + } + return new ComponentName(packageName, info.activityInfo.name); + } + } + return null; + } + + public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { + return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId); + } + + /** + * Important: The provided filterCallingUid is used exclusively to filter out activities + * that can be seen based on user state. It's typically the original caller uid prior + * to clearing. Because it can only be provided by trusted code, its value can be + * trusted and will be used as-is; unlike userId which will be validated by this method. + */ + public ActivityInfo getActivityInfoInternal(ComponentName component, int flags, + int filterCallingUid, int userId) { + if (!mUserManager.exists(userId)) return null; + flags = updateFlagsForComponent(flags, userId); + + if (!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId)) { + enforceCrossUserPermission(Binder.getCallingUid(), userId, + false /* requireFullPermission */, false /* checkShell */, + "get activity info"); + } + + return getActivityInfoInternalBody(component, flags, filterCallingUid, userId); + } + + public ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags, + int filterCallingUid, int userId) { + ParsedActivity a = mComponentResolver.getActivity(component); + + if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a); + + AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName()); + if (pkg != null && mSettings.isEnabledAndMatchLPr(pkg, a, flags, userId)) { + PackageSetting ps = mSettings.getPackageLPr(component.getPackageName()); + if (ps == null) return null; + if (shouldFilterApplicationLocked( + ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) { + return null; + } + return PackageInfoUtils.generateActivityInfo(pkg, + a, flags, ps.readUserState(userId), userId, ps); + } + if (resolveComponentName().equals(component)) { + return PackageParser.generateActivityInfo( + mResolveActivity, flags, new PackageUserState(), userId); + } + return null; + } + + public AndroidPackage getPackage(String packageName) { + packageName = resolveInternalPackageNameLPr( + packageName, PackageManager.VERSION_CODE_HIGHEST); + return mPackages.get(packageName); + } + + public AndroidPackage getPackage(int uid) { + final String[] packageNames = getPackagesForUidInternal(uid, Process.SYSTEM_UID); + AndroidPackage pkg = null; + final int numPackages = packageNames == null ? 0 : packageNames.length; + for (int i = 0; pkg == null && i < numPackages; i++) { + pkg = mPackages.get(packageNames[i]); + } + return pkg; + } + + public ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags, + int filterCallingUid, int userId) { + if (!mUserManager.exists(userId)) return null; + PackageSetting ps = mSettings.getPackageLPr(packageName); + if (ps != null) { + if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) { + return null; + } + if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) { + return null; + } + if (ps.pkg == null) { + final PackageInfo pInfo = generatePackageInfo(ps, flags, userId); + if (pInfo != null) { + return pInfo.applicationInfo; + } + return null; + } + ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(ps.pkg, flags, + ps.readUserState(userId), userId, ps); + if (ai != null) { + ai.packageName = resolveExternalPackageNameLPr(ps.pkg); + } + return ai; + } + return null; + } + + public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) { + return getApplicationInfoInternal(packageName, flags, Binder.getCallingUid(), userId); + } + + /** + * Important: The provided filterCallingUid is used exclusively to filter out applications + * that can be seen based on user state. It's typically the original caller uid prior + * to clearing. Because it can only be provided by trusted code, its value can be + * trusted and will be used as-is; unlike userId which will be validated by this method. + */ + public ApplicationInfo getApplicationInfoInternal(String packageName, int flags, + int filterCallingUid, int userId) { + if (!mUserManager.exists(userId)) return null; + flags = updateFlagsForApplication(flags, userId); + + if (!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId)) { + enforceCrossUserPermission(Binder.getCallingUid(), userId, + false /* requireFullPermission */, false /* checkShell */, + "get application info"); + } + + return getApplicationInfoInternalBody(packageName, flags, filterCallingUid, userId); + } + + public ApplicationInfo getApplicationInfoInternalBody(String packageName, int flags, + int filterCallingUid, int userId) { + // writer + // Normalize package name to handle renamed packages and static libs + packageName = resolveInternalPackageNameLPr(packageName, + PackageManager.VERSION_CODE_HIGHEST); + + AndroidPackage p = mPackages.get(packageName); + if (DEBUG_PACKAGE_INFO) Log.v( + TAG, "getApplicationInfo " + packageName + + ": " + p); + if (p != null) { + PackageSetting ps = mSettings.getPackageLPr(packageName); + if (ps == null) return null; + if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) { + return null; + } + if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) { + return null; + } + // Note: isEnabledLP() does not apply here - always return info + ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo( + p, flags, ps.readUserState(userId), userId, ps); + if (ai != null) { + ai.packageName = resolveExternalPackageNameLPr(p); + } + return ai; + } + if ((flags & PackageManager.MATCH_APEX) != 0) { + // For APKs, PackageInfo.applicationInfo is not exactly the same as ApplicationInfo + // returned from getApplicationInfo, but for APEX packages difference shouldn't be + // very big. + // TODO(b/155328545): generate proper application info for APEXes as well. + int apexFlags = ApexManager.MATCH_ACTIVE_PACKAGE; + if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) { + apexFlags = ApexManager.MATCH_FACTORY_PACKAGE; + } + final PackageInfo pi = mApexManager.getPackageInfo(packageName, apexFlags); + if (pi == null) { + return null; + } + return pi.applicationInfo; + } + if ("android".equals(packageName)||"system".equals(packageName)) { + return androidApplication(); + } + if ((flags & MATCH_KNOWN_PACKAGES) != 0) { + // Already generates the external package name + return generateApplicationInfoFromSettingsLPw(packageName, + flags, filterCallingUid, userId); + } + return null; + } + + public ArrayList<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPrBody( + Intent intent, int matchFlags, List<ResolveInfo> candidates, + CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) { + final ArrayList<ResolveInfo> result = new ArrayList<>(); + final ArrayList<ResolveInfo> alwaysList = new ArrayList<>(); + final ArrayList<ResolveInfo> undefinedList = new ArrayList<>(); + final ArrayList<ResolveInfo> alwaysAskList = new ArrayList<>(); + final ArrayList<ResolveInfo> neverList = new ArrayList<>(); + final ArrayList<ResolveInfo> matchAllList = new ArrayList<>(); + final int count = candidates.size(); + // First, try to use linked apps. Partition the candidates into four lists: + // one for the final results, one for the "do not use ever", one for "undefined status" + // and finally one for "browser app type". + for (int n=0; n<count; n++) { + ResolveInfo info = candidates.get(n); + String packageName = info.activityInfo.packageName; + PackageSetting ps = mSettings.getPackageLPr(packageName); + if (ps != null) { + // Add to the special match all list (Browser use case) + if (info.handleAllWebDataURI) { + matchAllList.add(info); + continue; + } + // Try to get the status from User settings first + long packedStatus = getDomainVerificationStatusLPr(ps, userId); + int status = (int)(packedStatus >> 32); + int linkGeneration = (int)(packedStatus & 0xFFFFFFFF); + if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) { + if (DEBUG_DOMAIN_VERIFICATION || debug) { + Slog.i(TAG, " + always: " + info.activityInfo.packageName + + " : linkgen=" + linkGeneration); + } + + if (!intent.hasCategory(CATEGORY_BROWSABLE) + || !intent.hasCategory(CATEGORY_DEFAULT)) { + undefinedList.add(info); + continue; + } + + // Use link-enabled generation as preferredOrder, i.e. + // prefer newly-enabled over earlier-enabled. + info.preferredOrder = linkGeneration; + alwaysList.add(info); + } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { + if (DEBUG_DOMAIN_VERIFICATION || debug) { + Slog.i(TAG, " + never: " + info.activityInfo.packageName); + } + neverList.add(info); + } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) { + if (DEBUG_DOMAIN_VERIFICATION || debug) { + Slog.i(TAG, " + always-ask: " + info.activityInfo.packageName); + } + alwaysAskList.add(info); + } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED || + status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK) { + if (DEBUG_DOMAIN_VERIFICATION || debug) { + Slog.i(TAG, " + ask: " + info.activityInfo.packageName); + } + undefinedList.add(info); + } + } + } + + // We'll want to include browser possibilities in a few cases + boolean includeBrowser = false; + + // First try to add the "always" resolution(s) for the current user, if any + if (alwaysList.size() > 0) { + result.addAll(alwaysList); + } else { + // Add all undefined apps as we want them to appear in the disambiguation dialog. + result.addAll(undefinedList); + // Maybe add one for the other profile. + if (xpDomainInfo != null && ( + xpDomainInfo.bestDomainVerificationStatus + != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)) { + result.add(xpDomainInfo.resolveInfo); + } + includeBrowser = true; + } + + // The presence of any 'always ask' alternatives means we'll also offer browsers. + // If there were 'always' entries their preferred order has been set, so we also + // back that off to make the alternatives equivalent + if (alwaysAskList.size() > 0) { + for (ResolveInfo i : result) { + i.preferredOrder = 0; + } + result.addAll(alwaysAskList); + includeBrowser = true; + } + + if (includeBrowser) { + // Also add browsers (all of them or only the default one) + if (DEBUG_DOMAIN_VERIFICATION) { + Slog.v(TAG, " ...including browsers in candidate set"); + } + if ((matchFlags & MATCH_ALL) != 0) { + result.addAll(matchAllList); + } else { + // Browser/generic handling case. If there's a default browser, go straight + // to that (but only if there is no other higher-priority match). + final String defaultBrowserPackageName = mDefaultAppProvider.getDefaultBrowser( + userId); + int maxMatchPrio = 0; + ResolveInfo defaultBrowserMatch = null; + final int numCandidates = matchAllList.size(); + for (int n = 0; n < numCandidates; n++) { + ResolveInfo info = matchAllList.get(n); + // track the highest overall match priority... + if (info.priority > maxMatchPrio) { + maxMatchPrio = info.priority; + } + // ...and the highest-priority default browser match + if (info.activityInfo.packageName.equals(defaultBrowserPackageName)) { + if (defaultBrowserMatch == null + || (defaultBrowserMatch.priority < info.priority)) { + if (debug) { + Slog.v(TAG, "Considering default browser match " + info); + } + defaultBrowserMatch = info; + } + } + } + if (defaultBrowserMatch != null + && defaultBrowserMatch.priority >= maxMatchPrio + && !TextUtils.isEmpty(defaultBrowserPackageName)) + { + if (debug) { + Slog.v(TAG, "Default browser match " + defaultBrowserMatch); + } + result.add(defaultBrowserMatch); + } else { + result.addAll(matchAllList); + } + } + + // If there is nothing selected, add all candidates and remove the ones that the + //user + // has explicitly put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state + if (result.size() == 0) { + result.addAll(candidates); + result.removeAll(neverList); + } + } + return result; + } + + /** + * Report the 'Home' activity which is currently set as "always use this one". If non is set + * then reports the most likely home activity or null if there are more than one. + */ + public ComponentName getDefaultHomeActivity(int userId) { + List<ResolveInfo> allHomeCandidates = new ArrayList<>(); + ComponentName cn = getHomeActivitiesAsUser(allHomeCandidates, userId); + if (cn != null) { + return cn; + } + // TODO: This should not happen since there should always be a default package set for + // ROLE_HOME in RoleManager. Continue with a warning log for now. + Slog.w(TAG, "Default package for ROLE_HOME is not set in RoleManager"); + + // Find the launcher with the highest priority and return that component if there are no + // other home activity with the same priority. + int lastPriority = Integer.MIN_VALUE; + ComponentName lastComponent = null; + final int size = allHomeCandidates.size(); + for (int i = 0; i < size; i++) { + final ResolveInfo ri = allHomeCandidates.get(i); + if (ri.priority > lastPriority) { + lastComponent = ri.activityInfo.getComponentName(); + lastPriority = ri.priority; + } else if (ri.priority == lastPriority) { + // Two components found with same priority. + lastComponent = null; + } + } + return lastComponent; + } + + public ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates, + int userId) { + Intent intent = getHomeIntent(); + List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null, + PackageManager.GET_META_DATA, userId); + allHomeCandidates.clear(); + if (resolveInfos == null) { + return null; + } + allHomeCandidates.addAll(resolveInfos); + + final String packageName = mDefaultAppProvider.getDefaultHome(userId); + if (packageName == null) { + return null; + } + + int resolveInfosSize = resolveInfos.size(); + for (int i = 0; i < resolveInfosSize; i++) { + ResolveInfo resolveInfo = resolveInfos.get(i); + + if (resolveInfo.activityInfo != null && TextUtils.equals( + resolveInfo.activityInfo.packageName, packageName)) { + return new ComponentName(resolveInfo.activityInfo.packageName, + resolveInfo.activityInfo.name); + } + } + return null; + } + + public CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent, + String resolvedType, int flags, int sourceUserId, int parentUserId) { + if (!mUserManager.hasUserRestriction(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, + sourceUserId)) { + return null; + } + List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent, + resolvedType, flags, parentUserId); + + if (resultTargetUser == null || resultTargetUser.isEmpty()) { + return null; + } + CrossProfileDomainInfo result = null; + int size = resultTargetUser.size(); + for (int i = 0; i < size; i++) { + ResolveInfo riTargetUser = resultTargetUser.get(i); + // Intent filter verification is only for filters that specify a host. So don't + //return + // those that handle all web uris. + if (riTargetUser.handleAllWebDataURI) { + continue; + } + String packageName = riTargetUser.activityInfo.packageName; + PackageSetting ps = mSettings.getPackageLPr(packageName); + if (ps == null) { + continue; + } + long verificationState = getDomainVerificationStatusLPr(ps, parentUserId); + int status = (int)(verificationState >> 32); + if (result == null) { + result = new CrossProfileDomainInfo(); + result.resolveInfo = createForwardingResolveInfoUnchecked(new IntentFilter(), + sourceUserId, parentUserId); + result.bestDomainVerificationStatus = status; + } else { + result.bestDomainVerificationStatus = bestDomainVerificationStatus(status, + result.bestDomainVerificationStatus); + } + } + // Don't consider matches with status NEVER across profiles. + if (result != null && result.bestDomainVerificationStatus + == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { + return null; + } + return result; + } + + public Intent getHomeIntent() { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + intent.addCategory(Intent.CATEGORY_DEFAULT); + return intent; + } + + public List<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(Intent intent, + String resolvedType, int userId) { + CrossProfileIntentResolver resolver = mSettings.getCrossProfileIntentResolver(userId); + if (resolver != null) { + return resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/, userId); + } + return null; + } + + /** + * Filters out ephemeral activities. + * <p>When resolving for an ephemeral app, only activities that 1) are defined in the + * ephemeral app or 2) marked with {@code visibleToEphemeral} are returned. + * + * @param resolveInfos The pre-filtered list of resolved activities + * @param ephemeralPkgName The ephemeral package name. If {@code null}, no filtering + * is performed. + * @param intent + * @return A filtered list of resolved activities. + */ + public List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos, + String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid, + boolean resolveForStart, int userId, Intent intent) { + final boolean blockInstant = intent.isWebIntent() && areWebInstantAppsDisabled(userId); + for (int i = resolveInfos.size() - 1; i >= 0; i--) { + final ResolveInfo info = resolveInfos.get(i); + // remove locally resolved instant app web results when disabled + if (info.isInstantAppAvailable && blockInstant) { + resolveInfos.remove(i); + continue; + } + // allow activities that are defined in the provided package + if (allowDynamicSplits + && info.activityInfo != null + && info.activityInfo.splitName != null + && !ArrayUtils.contains(info.activityInfo.applicationInfo.splitNames, + info.activityInfo.splitName)) { + if (instantAppInstallerActivity() == null) { + if (DEBUG_INSTALL) { + Slog.v(TAG, "No installer - not adding it to the ResolveInfo list"); + } + resolveInfos.remove(i); + continue; + } + if (blockInstant && isInstantApp(info.activityInfo.packageName, userId)) { + resolveInfos.remove(i); + continue; + } + // requested activity is defined in a split that hasn't been installed yet. + // add the installer to the resolve list + if (DEBUG_INSTALL) { + Slog.v(TAG, "Adding installer to the ResolveInfo list"); + } + final ResolveInfo installerInfo = new ResolveInfo( + mInstantAppInstallerInfo); + final ComponentName installFailureActivity = findInstallFailureActivity( + info.activityInfo.packageName, filterCallingUid, userId); + installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( + installFailureActivity, + info.activityInfo.packageName, + info.activityInfo.applicationInfo.longVersionCode, + info.activityInfo.splitName); + // add a non-generic filter + installerInfo.filter = new IntentFilter(); + + // This resolve info may appear in the chooser UI, so let us make it + // look as the one it replaces as far as the user is concerned which + // requires loading the correct label and icon for the resolve info. + installerInfo.resolvePackageName = info.getComponentInfo().packageName; + installerInfo.labelRes = info.resolveLabelResId(); + installerInfo.icon = info.resolveIconResId(); + installerInfo.isInstantAppAvailable = true; + resolveInfos.set(i, installerInfo); + continue; + } + if (ephemeralPkgName == null) { + // caller is a full app + SettingBase callingSetting = + mSettings.getSettingLPr(UserHandle.getAppId(filterCallingUid)); + PackageSetting resolvedSetting = + getPackageSettingInternal(info.activityInfo.packageName, 0); + if (resolveForStart + || !mAppsFilter.shouldFilterApplication( + filterCallingUid, callingSetting, resolvedSetting, userId)) { + continue; + } + } else if (ephemeralPkgName.equals(info.activityInfo.packageName)) { + // caller is same app; don't need to apply any other filtering + continue; + } else if (resolveForStart + && (intent.isWebIntent() + || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) + && intent.getPackage() == null + && intent.getComponent() == null) { + // ephemeral apps can launch other ephemeral apps indirectly + continue; + } else if (((info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) + != 0) + && !info.activityInfo.applicationInfo.isInstantApp()) { + // allow activities that have been explicitly exposed to ephemeral apps + continue; + } + resolveInfos.remove(i); + } + return resolveInfos; + } + + public List<ResolveInfo> applyPostServiceResolutionFilter(List<ResolveInfo> resolveInfos, + String instantAppPkgName, @UserIdInt int userId, int filterCallingUid) { + for (int i = resolveInfos.size() - 1; i >= 0; i--) { + final ResolveInfo info = resolveInfos.get(i); + if (instantAppPkgName == null) { + SettingBase callingSetting = + mSettings.getSettingLPr(UserHandle.getAppId(filterCallingUid)); + PackageSetting resolvedSetting = + getPackageSettingInternal(info.serviceInfo.packageName, 0); + if (!mAppsFilter.shouldFilterApplication( + filterCallingUid, callingSetting, resolvedSetting, userId)) { + continue; + } + } + final boolean isEphemeralApp = info.serviceInfo.applicationInfo.isInstantApp(); + // allow services that are defined in the provided package + if (isEphemeralApp && instantAppPkgName.equals(info.serviceInfo.packageName)) { + if (info.serviceInfo.splitName != null + && !ArrayUtils.contains(info.serviceInfo.applicationInfo.splitNames, + info.serviceInfo.splitName)) { + // requested service is defined in a split that hasn't been installed yet. + // add the installer to the resolve list + if (DEBUG_INSTANT) { + Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); + } + final ResolveInfo installerInfo = new ResolveInfo( + mInstantAppInstallerInfo); + installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( + null /* installFailureActivity */, + info.serviceInfo.packageName, + info.serviceInfo.applicationInfo.longVersionCode, + info.serviceInfo.splitName); + // add a non-generic filter + installerInfo.filter = new IntentFilter(); + // load resources from the correct package + installerInfo.resolvePackageName = info.getComponentInfo().packageName; + resolveInfos.set(i, installerInfo); + } + continue; + } + // allow services that have been explicitly exposed to ephemeral apps + if (!isEphemeralApp + && ((info.serviceInfo.flags & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) + != 0)) { + continue; + } + resolveInfos.remove(i); + } + return resolveInfos; + } + + public List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent, + int matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo, + int userId) { + final boolean debug = (intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0; + + if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) { + Slog.v(TAG, "Filtering results with preferred activities. Candidates count: " + + candidates.size()); + } + + final ArrayList<ResolveInfo> result = + filterCandidatesWithDomainPreferredActivitiesLPrBody( + intent, matchFlags, candidates, xpDomainInfo, userId, debug); + + if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) { + Slog.v(TAG, "Filtered results with preferred activities. New candidates count: " + + result.size()); + for (ResolveInfo info : result) { + Slog.v(TAG, " + " + info.activityInfo); + } + } + return result; + } + + /** + * Filter out activities with systemUserOnly flag set, when current user is not System. + * + * @return filtered list + */ + public List<ResolveInfo> filterIfNotSystemUser(List<ResolveInfo> resolveInfos, int userId) { + if (userId == UserHandle.USER_SYSTEM) { + return resolveInfos; + } + for (int i = resolveInfos.size() - 1; i >= 0; i--) { + ResolveInfo info = resolveInfos.get(i); + if ((info.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) { + resolveInfos.remove(i); + } + } + return resolveInfos; + } + + public List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result, + Intent intent, + String resolvedType, int flags, int userId, boolean resolveForStart, + boolean isRequesterInstantApp) { + // first, check to see if we've got an instant app already installed + final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0; + ResolveInfo localInstantApp = null; + boolean blockResolution = false; + if (!alreadyResolvedLocally) { + final List<ResolveInfo> instantApps = mComponentResolver.queryActivities( + intent, + resolvedType, + flags + | PackageManager.GET_RESOLVED_FILTER + | PackageManager.MATCH_INSTANT + | PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY, + userId); + for (int i = instantApps.size() - 1; i >= 0; --i) { + final ResolveInfo info = instantApps.get(i); + final String packageName = info.activityInfo.packageName; + final PackageSetting ps = mSettings.getPackageLPr(packageName); + if (ps.getInstantApp(userId)) { + final long packedStatus = getDomainVerificationStatusLPr(ps, userId); + final int status = (int)(packedStatus >> 32); + if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { + // there's a local instant application installed, but, the user has + // chosen to never use it; skip resolution and don't acknowledge + // an instant application is even available + if (DEBUG_INSTANT) { + Slog.v(TAG, "Instant app marked to never run; pkg: " + packageName); + } + blockResolution = true; + break; + } else { + // we have a locally installed instant application; skip resolution + // but acknowledge there's an instant application available + if (DEBUG_INSTANT) { + Slog.v(TAG, "Found installed instant app; pkg: " + packageName); + } + localInstantApp = info; + break; + } + } + } + } + // no app installed, let's see if one's available + AuxiliaryResolveInfo auxiliaryResponse = null; + if (!blockResolution) { + if (localInstantApp == null) { + // we don't have an instant app locally, resolve externally + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral"); + String token = UUID.randomUUID().toString(); + InstantAppDigest digest = InstantAppResolver.parseDigest(intent); + final InstantAppRequest requestObject = + new InstantAppRequest(null /*responseObj*/, + intent /*origIntent*/, resolvedType, null /*callingPackage*/, + null /*callingFeatureId*/, isRequesterInstantApp, userId, + null /*verificationBundle*/, resolveForStart, + digest.getDigestPrefixSecure(), token); + auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne( + mInstantAppResolverConnection, requestObject); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } else { + // we have an instant application locally, but, we can't admit that since + // callers shouldn't be able to determine prior browsing. create a placeholder + // auxiliary response so the downstream code behaves as if there's an + // instant application available externally. when it comes time to start + // the instant application, we'll do the right thing. + final ApplicationInfo ai = localInstantApp.activityInfo.applicationInfo; + auxiliaryResponse = new AuxiliaryResolveInfo(null /* failureActivity */, + ai.packageName, ai.longVersionCode, + null /* splitName */); + } + } + if (intent.isWebIntent() && auxiliaryResponse == null) { + return result; + } + final PackageSetting ps = + mSettings.getPackageLPr(instantAppInstallerActivity().packageName); + if (ps == null + || !ps.readUserState(userId).isEnabled(instantAppInstallerActivity(), 0)) { + return result; + } + final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo); + ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo( + instantAppInstallerActivity(), 0, ps.readUserState(userId), userId); + ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART + | IntentFilter.MATCH_ADJUSTMENT_NORMAL; + // add a non-generic filter + ephemeralInstaller.filter = new IntentFilter(); + if (intent.getAction() != null) { + ephemeralInstaller.filter.addAction(intent.getAction()); + } + if (intent.getData() != null && intent.getData().getPath() != null) { + ephemeralInstaller.filter.addDataPath( + intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL); + } + ephemeralInstaller.isInstantAppAvailable = true; + // make sure this resolver is the default + ephemeralInstaller.isDefault = true; + ephemeralInstaller.auxiliaryInfo = auxiliaryResponse; + if (DEBUG_INSTANT) { + Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); + } + + result.add(ephemeralInstaller); + return result; + } + + public PackageInfo generatePackageInfo(PackageSetting ps, int flags, int userId) { + if (!mUserManager.exists(userId)) return null; + if (ps == null) { + return null; + } + final int callingUid = Binder.getCallingUid(); + // Filter out ephemeral app metadata: + // * The system/shell/root can see metadata for any app + // * An installed app can see metadata for 1) other installed apps + // and 2) ephemeral apps that have explicitly interacted with it + // * Ephemeral apps can only see their own data and exposed installed apps + // * Holding a signature permission allows seeing instant apps + if (shouldFilterApplicationLocked(ps, callingUid, userId)) { + return null; + } + + if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0 + && ps.isSystem()) { + flags |= MATCH_ANY_USER; + } + + final PackageUserState state = ps.readUserState(userId); + AndroidPackage p = ps.pkg; + if (p != null) { + // Compute GIDs only if requested + final int[] gids = (flags & PackageManager.GET_GIDS) == 0 ? EMPTY_INT_ARRAY + : mPermissionManager.getGidsForUid(UserHandle.getUid(userId, ps.appId)); + // Compute granted permissions only if package has requested permissions + final Set<String> permissions = ArrayUtils.isEmpty(p.getRequestedPermissions()) + ? Collections.emptySet() + : mPermissionManager.getGrantedPermissions(ps.name, userId); + + PackageInfo packageInfo = PackageInfoUtils.generate(p, gids, flags, + ps.firstInstallTime, ps.lastUpdateTime, permissions, state, userId, ps); + + if (packageInfo == null) { + return null; + } + + packageInfo.packageName = packageInfo.applicationInfo.packageName = + resolveExternalPackageNameLPr(p); + + return packageInfo; + } else if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0 && state.isAvailable(flags)) { + PackageInfo pi = new PackageInfo(); + pi.packageName = ps.name; + pi.setLongVersionCode(ps.versionCode); + pi.sharedUserId = (ps.sharedUser != null) ? ps.sharedUser.name : null; + pi.firstInstallTime = ps.firstInstallTime; + pi.lastUpdateTime = ps.lastUpdateTime; + + ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = ps.name; + ai.uid = UserHandle.getUid(userId, ps.appId); + ai.primaryCpuAbi = ps.primaryCpuAbiString; + ai.secondaryCpuAbi = ps.secondaryCpuAbiString; + ai.setVersionCode(ps.versionCode); + ai.flags = ps.pkgFlags; + ai.privateFlags = ps.pkgPrivateFlags; + pi.applicationInfo = + PackageParser.generateApplicationInfo(ai, flags, state, userId); + + if (DEBUG_PACKAGE_INFO) Log.v(TAG, "ps.pkg is n/a for [" + + ps.name + "]. Provides a minimum info."); + return pi; + } else { + return null; + } + } + + public PackageInfo getPackageInfo(String packageName, int flags, int userId) { + return getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST, + flags, Binder.getCallingUid(), userId); + } + + /** + * Important: The provided filterCallingUid is used exclusively to filter out packages + * that can be seen based on user state. It's typically the original caller uid prior + * to clearing. Because it can only be provided by trusted code, its value can be + * trusted and will be used as-is; unlike userId which will be validated by this method. + */ + public PackageInfo getPackageInfoInternal(String packageName, long versionCode, + int flags, int filterCallingUid, int userId) { + if (!mUserManager.exists(userId)) return null; + flags = updateFlagsForPackage(flags, userId); + enforceCrossUserPermission(Binder.getCallingUid(), userId, + false /* requireFullPermission */, false /* checkShell */, "get package info"); + + return getPackageInfoInternalBody(packageName, versionCode, flags, filterCallingUid, + userId); + } + + public PackageInfo getPackageInfoInternalBody(String packageName, long versionCode, + int flags, int filterCallingUid, int userId) { + // reader + // Normalize package name to handle renamed packages and static libs + packageName = resolveInternalPackageNameLPr(packageName, versionCode); + + final boolean matchFactoryOnly = (flags & MATCH_FACTORY_ONLY) != 0; + if (matchFactoryOnly) { + // Instant app filtering for APEX modules is ignored + if ((flags & MATCH_APEX) != 0) { + return mApexManager.getPackageInfo(packageName, + ApexManager.MATCH_FACTORY_PACKAGE); + } + final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName); + if (ps != null) { + if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) { + return null; + } + if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) { + return null; + } + return generatePackageInfo(ps, flags, userId); + } + } + + AndroidPackage p = mPackages.get(packageName); + if (matchFactoryOnly && p != null && !p.isSystem()) { + return null; + } + if (DEBUG_PACKAGE_INFO) + Log.v(TAG, "getPackageInfo " + packageName + ": " + p); + if (p != null) { + final PackageSetting ps = getPackageSetting(p.getPackageName()); + if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) { + return null; + } + if (ps != null && shouldFilterApplicationLocked(ps, filterCallingUid, userId)) { + return null; + } + + return generatePackageInfo(ps, flags, userId); + } + if (!matchFactoryOnly && (flags & MATCH_KNOWN_PACKAGES) != 0) { + final PackageSetting ps = mSettings.getPackageLPr(packageName); + if (ps == null) return null; + if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) { + return null; + } + if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) { + return null; + } + return generatePackageInfo(ps, flags, userId); + } + if ((flags & MATCH_APEX) != 0) { + return mApexManager.getPackageInfo(packageName, ApexManager.MATCH_ACTIVE_PACKAGE); + } + return null; + } + + @Nullable + public PackageSetting getPackageSetting(String packageName) { + return getPackageSettingInternal(packageName, Binder.getCallingUid()); + } + + public PackageSetting getPackageSettingInternal(String packageName, int callingUid) { + packageName = resolveInternalPackageNameInternalLocked( + packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid); + return mSettings.getPackageLPr(packageName); + } + + public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) { + final int callingUid = Binder.getCallingUid(); + if (getInstantAppPackageName(callingUid) != null) { + return ParceledListSlice.emptyList(); + } + if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList(); + flags = updateFlagsForPackage(flags, userId); + + enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */, + false /* checkShell */, "get installed packages"); + + return getInstalledPackagesBody(flags, userId, callingUid); + } + + public ParceledListSlice<PackageInfo> getInstalledPackagesBody(int flags, int userId, + int callingUid) { + // writer + final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0; + final boolean listApex = (flags & MATCH_APEX) != 0; + final boolean listFactory = (flags & MATCH_FACTORY_ONLY) != 0; + + ArrayList<PackageInfo> list; + if (listUninstalled) { + list = new ArrayList<>(mSettings.getPackagesLocked().size()); + for (PackageSetting ps : mSettings.getPackagesLocked().values()) { + if (listFactory) { + if (!ps.isSystem()) { + continue; + } + PackageSetting psDisabled = mSettings.getDisabledSystemPkgLPr(ps); + if (psDisabled != null) { + ps = psDisabled; + } + } + if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) { + continue; + } + if (shouldFilterApplicationLocked(ps, callingUid, userId)) { + continue; + } + final PackageInfo pi = generatePackageInfo(ps, flags, userId); + if (pi != null) { + list.add(pi); + } + } + } else { + list = new ArrayList<>(mPackages.size()); + for (AndroidPackage p : mPackages.values()) { + PackageSetting ps = getPackageSetting(p.getPackageName()); + if (listFactory) { + if (!p.isSystem()) { + continue; + } + PackageSetting psDisabled = mSettings.getDisabledSystemPkgLPr(ps); + if (psDisabled != null) { + ps = psDisabled; + } + } + if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) { + continue; + } + if (shouldFilterApplicationLocked(ps, callingUid, userId)) { + continue; + } + final PackageInfo pi = generatePackageInfo(ps, flags, userId); + if (pi != null) { + list.add(pi); + } + } + } + if (listApex) { + if (listFactory) { + list.addAll(mApexManager.getFactoryPackages()); + } else { + list.addAll(mApexManager.getActivePackages()); + } + if (listUninstalled) { + list.addAll(mApexManager.getInactivePackages()); + } + } + return new ParceledListSlice<>(list); + } + + /** + * If the filter's target user can handle the intent and is enabled: returns a ResolveInfo + * that + * will forward the intent to the filter's target user. + * Otherwise, returns null. + */ + public ResolveInfo createForwardingResolveInfo(CrossProfileIntentFilter filter, + Intent intent, + String resolvedType, int flags, int sourceUserId) { + int targetUserId = filter.getTargetUserId(); + List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent, + resolvedType, flags, targetUserId); + if (resultTargetUser != null && isUserEnabled(targetUserId)) { + // If all the matches in the target profile are suspended, return null. + for (int i = resultTargetUser.size() - 1; i >= 0; i--) { + if ((resultTargetUser.get(i).activityInfo.applicationInfo.flags + & ApplicationInfo.FLAG_SUSPENDED) == 0) { + return createForwardingResolveInfoUnchecked(filter, sourceUserId, + targetUserId); + } + } + } + return null; + } + + public ResolveInfo createForwardingResolveInfoUnchecked(IntentFilter filter, + int sourceUserId, int targetUserId) { + ResolveInfo forwardingResolveInfo = new ResolveInfo(); + final long ident = Binder.clearCallingIdentity(); + boolean targetIsProfile; + try { + targetIsProfile = mUserManager.getUserInfo(targetUserId).isManagedProfile(); + } finally { + Binder.restoreCallingIdentity(ident); + } + String className; + if (targetIsProfile) { + className = FORWARD_INTENT_TO_MANAGED_PROFILE; + } else { + className = FORWARD_INTENT_TO_PARENT; + } + ComponentName forwardingActivityComponentName = new ComponentName( + androidApplication().packageName, className); + ActivityInfo forwardingActivityInfo = + getActivityInfo(forwardingActivityComponentName, 0, + sourceUserId); + if (!targetIsProfile) { + forwardingActivityInfo.showUserIcon = targetUserId; + forwardingResolveInfo.noResourceId = true; + } + forwardingResolveInfo.activityInfo = forwardingActivityInfo; + forwardingResolveInfo.priority = 0; + forwardingResolveInfo.preferredOrder = 0; + forwardingResolveInfo.match = 0; + forwardingResolveInfo.isDefault = true; + forwardingResolveInfo.filter = filter; + forwardingResolveInfo.targetUserId = targetUserId; + return forwardingResolveInfo; + } + + // Return matching ResolveInfo in target user if any. + public ResolveInfo queryCrossProfileIntents( + List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, + int flags, int sourceUserId, boolean matchInCurrentProfile) { + if (matchingFilters != null) { + // Two {@link CrossProfileIntentFilter}s can have the same targetUserId and + // match the same intent. For performance reasons, it is better not to + // run queryIntent twice for the same userId + SparseBooleanArray alreadyTriedUserIds = new SparseBooleanArray(); + int size = matchingFilters.size(); + for (int i = 0; i < size; i++) { + CrossProfileIntentFilter filter = matchingFilters.get(i); + int targetUserId = filter.getTargetUserId(); + boolean skipCurrentProfile = + (filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0; + boolean skipCurrentProfileIfNoMatchFound = + (filter.getFlags() & PackageManager.ONLY_IF_NO_MATCH_FOUND) != 0; + if (!skipCurrentProfile && !alreadyTriedUserIds.get(targetUserId) + && (!skipCurrentProfileIfNoMatchFound || !matchInCurrentProfile)) { + // Checking if there are activities in the target user that can handle the + // intent. + ResolveInfo resolveInfo = createForwardingResolveInfo(filter, intent, + resolvedType, flags, sourceUserId); + if (resolveInfo != null) return resolveInfo; + alreadyTriedUserIds.put(targetUserId, true); + } + } + } + return null; + } + + public ResolveInfo querySkipCurrentProfileIntents( + List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, + int flags, int sourceUserId) { + if (matchingFilters != null) { + int size = matchingFilters.size(); + for (int i = 0; i < size; i ++) { + CrossProfileIntentFilter filter = matchingFilters.get(i); + if ((filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0) { + // Checking if there are activities in the target user that can handle the + // intent. + ResolveInfo resolveInfo = createForwardingResolveInfo(filter, intent, + resolvedType, flags, sourceUserId); + if (resolveInfo != null) { + return resolveInfo; + } + } + } + } + return null; + } + + public ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) { + if (!mUserManager.exists(userId)) return null; + final int callingUid = Binder.getCallingUid(); + flags = updateFlagsForComponent(flags, userId); + enforceCrossUserOrProfilePermission(callingUid, userId, + false /* requireFullPermission */, + false /* checkShell */, "get service info"); + return getServiceInfoBody(component, flags, userId, callingUid); + } + + public ServiceInfo getServiceInfoBody(ComponentName component, int flags, int userId, + int callingUid) { + ParsedService s = mComponentResolver.getService(component); + if (DEBUG_PACKAGE_INFO) Log.v( + TAG, "getServiceInfo " + component + ": " + s); + if (s == null) { + return null; + } + + AndroidPackage pkg = mPackages.get(s.getPackageName()); + if (mSettings.isEnabledAndMatchLPr(pkg, s, flags, userId)) { + PackageSetting ps = mSettings.getPackageLPr(component.getPackageName()); + if (ps == null) return null; + if (shouldFilterApplicationLocked( + ps, callingUid, component, TYPE_SERVICE, userId)) { + return null; + } + return PackageInfoUtils.generateServiceInfo(pkg, + s, flags, ps.readUserState(userId), userId, ps); + } + return null; + } + + @Nullable + public SharedLibraryInfo getSharedLibraryInfoLPr(String name, long version) { + return getSharedLibraryInfo(name, version, mSharedLibraries, null); + } + + /** + * Returns the package name of the calling Uid if it's an instant app. If it isn't + * instant, returns {@code null}. + */ + public String getInstantAppPackageName(int callingUid) { + // If the caller is an isolated app use the owner's uid for the lookup. + if (Process.isIsolated(callingUid)) { + callingUid = mIsolatedOwners.get(callingUid); + } + final int appId = UserHandle.getAppId(callingUid); + final Object obj = mSettings.getSettingLPr(appId); + if (obj instanceof PackageSetting) { + final PackageSetting ps = (PackageSetting) obj; + final boolean isInstantApp = ps.getInstantApp(UserHandle.getUserId(callingUid)); + return isInstantApp ? ps.pkg.getPackageName() : null; + } + return null; + } + + public String resolveExternalPackageNameLPr(AndroidPackage pkg) { + if (pkg.getStaticSharedLibName() != null) { + return pkg.getManifestPackageName(); + } + return pkg.getPackageName(); + } + + public String resolveInternalPackageNameInternalLocked( + String packageName, long versionCode, int callingUid) { + // Handle renamed packages + String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName); + packageName = normalizedPackageName != null ? normalizedPackageName : packageName; + + // Is this a static library? + LongSparseArray<SharedLibraryInfo> versionedLib = + mStaticLibsByDeclaringPackage.get(packageName); + if (versionedLib == null || versionedLib.size() <= 0) { + return packageName; + } + + // Figure out which lib versions the caller can see + LongSparseLongArray versionsCallerCanSee = null; + final int callingAppId = UserHandle.getAppId(callingUid); + if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.SHELL_UID + && callingAppId != Process.ROOT_UID) { + versionsCallerCanSee = new LongSparseLongArray(); + String libName = versionedLib.valueAt(0).getName(); + String[] uidPackages = getPackagesForUidInternal(callingUid, callingUid); + if (uidPackages != null) { + for (String uidPackage : uidPackages) { + PackageSetting ps = mSettings.getPackageLPr(uidPackage); + final int libIdx = ArrayUtils.indexOf(ps.usesStaticLibraries, libName); + if (libIdx >= 0) { + final long libVersion = ps.usesStaticLibrariesVersions[libIdx]; + versionsCallerCanSee.append(libVersion, libVersion); + } + } + } + } + + // Caller can see nothing - done + if (versionsCallerCanSee != null && versionsCallerCanSee.size() <= 0) { + return packageName; + } + + // Find the version the caller can see and the app version code + SharedLibraryInfo highestVersion = null; + final int versionCount = versionedLib.size(); + for (int i = 0; i < versionCount; i++) { + SharedLibraryInfo libraryInfo = versionedLib.valueAt(i); + if (versionsCallerCanSee != null && versionsCallerCanSee.indexOfKey( + libraryInfo.getLongVersion()) < 0) { + continue; + } + final long libVersionCode = libraryInfo.getDeclaringPackage().getLongVersionCode(); + if (versionCode != PackageManager.VERSION_CODE_HIGHEST) { + if (libVersionCode == versionCode) { + return libraryInfo.getPackageName(); + } + } else if (highestVersion == null) { + highestVersion = libraryInfo; + } else if (libVersionCode > highestVersion + .getDeclaringPackage().getLongVersionCode()) { + highestVersion = libraryInfo; + } + } + + if (highestVersion != null) { + return highestVersion.getPackageName(); + } + + return packageName; + } + + public String resolveInternalPackageNameLPr(String packageName, long versionCode) { + final int callingUid = Binder.getCallingUid(); + return resolveInternalPackageNameInternalLocked(packageName, versionCode, + callingUid); + } + + /** + * <em>IMPORTANT:</em> Not all packages returned by this method may be known + * to the system. There are two conditions in which this may occur: + * <ol> + * <li>The package is on adoptable storage and the device has been removed</li> + * <li>The package is being removed and the internal structures are partially updated</li> + * </ol> + * The second is an artifact of the current data structures and should be fixed. See + * b/111075456 for one such instance. + * This binder API is cached. If the algorithm in this method changes, + * or if the underlying objecs (as returned by getSettingLPr()) change + * then the logic that invalidates the cache must be revisited. See + * calls to invalidateGetPackagesForUidCache() to locate the points at + * which the cache is invalidated. + */ + public String[] getPackagesForUid(int uid) { + return getPackagesForUidInternal(uid, Binder.getCallingUid()); + } + + public String[] getPackagesForUidInternal(int uid, int callingUid) { + final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null; + final int userId = UserHandle.getUserId(uid); + final int appId = UserHandle.getAppId(uid); + return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp); + } + + public String[] getPackagesForUidInternalBody(int callingUid, int userId, int appId, + boolean isCallerInstantApp) { + // reader + final Object obj = mSettings.getSettingLPr(appId); + if (obj instanceof SharedUserSetting) { + if (isCallerInstantApp) { + return null; + } + final SharedUserSetting sus = (SharedUserSetting) obj; + final int N = sus.packages.size(); + String[] res = new String[N]; + int i = 0; + for (int index = 0; index < N; index++) { + final PackageSetting ps = sus.packages.valueAt(index); + if (ps.getInstalled(userId)) { + res[i++] = ps.name; + } + } + return ArrayUtils.trimToSize(res, i); + } else if (obj instanceof PackageSetting) { + final PackageSetting ps = (PackageSetting) obj; + if (ps.getInstalled(userId) + && !shouldFilterApplicationLocked(ps, callingUid, userId)) { + return new String[]{ps.name}; + } + } + return null; + } + + public UserInfo getProfileParent(int userId) { + final long identity = Binder.clearCallingIdentity(); + try { + return mUserManager.getProfileParent(userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Returns whether or not instant apps have been disabled remotely. + */ + public boolean areWebInstantAppsDisabled(int userId) { + return mWebInstantAppsDisabled.get(userId); + } + + /** + * Returns whether or not a full application can see an instant application. + * <p> + * Currently, there are four cases in which this can occur: + * <ol> + * <li>The calling application is a "special" process. Special processes + * are those with a UID < {@link Process#FIRST_APPLICATION_UID}.</li> + * <li>The calling application has the permission + * {@link android.Manifest.permission#ACCESS_INSTANT_APPS}.</li> + * <li>The calling application is the default launcher on the + * system partition.</li> + * <li>The calling application is the default app prediction service.</li> + * </ol> + */ + public boolean canViewInstantApps(int callingUid, int userId) { + if (callingUid < Process.FIRST_APPLICATION_UID) { + return true; + } + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.ACCESS_INSTANT_APPS) == PERMISSION_GRANTED) { + return true; + } + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.VIEW_INSTANT_APPS) == PERMISSION_GRANTED) { + final ComponentName homeComponent = getDefaultHomeActivity(userId); + if (homeComponent != null + && isCallerSameApp(homeComponent.getPackageName(), callingUid)) { + return true; + } + // TODO(b/122900055) Change/Remove this and replace with new permission role. + if (mAppPredictionServicePackage != null + && isCallerSameApp(mAppPredictionServicePackage, callingUid)) { + return true; + } + } + return false; + } + + public boolean filterSharedLibPackageLPr(@Nullable PackageSetting ps, int uid, int userId, + int flags) { + // Callers can access only the libs they depend on, otherwise they need to explicitly + // ask for the shared libraries given the caller is allowed to access all static libs. + if ((flags & PackageManager.MATCH_STATIC_SHARED_LIBRARIES) != 0) { + // System/shell/root get to see all static libs + final int appId = UserHandle.getAppId(uid); + if (appId == Process.SYSTEM_UID || appId == Process.SHELL_UID + || appId == Process.ROOT_UID) { + return false; + } + // Installer gets to see all static libs. + if (PackageManager.PERMISSION_GRANTED + == checkUidPermission(Manifest.permission.INSTALL_PACKAGES, uid)) { + return false; + } + } + + // No package means no static lib as it is always on internal storage + if (ps == null || ps.pkg == null || !ps.pkg.isStaticSharedLibrary()) { + return false; + } + + final SharedLibraryInfo libraryInfo = getSharedLibraryInfoLPr( + ps.pkg.getStaticSharedLibName(), ps.pkg.getStaticSharedLibVersion()); + if (libraryInfo == null) { + return false; + } + + final int resolvedUid = UserHandle.getUid(userId, UserHandle.getAppId(uid)); + final String[] uidPackageNames = getPackagesForUid(resolvedUid); + if (uidPackageNames == null) { + return true; + } + + for (String uidPackageName : uidPackageNames) { + if (ps.name.equals(uidPackageName)) { + return false; + } + PackageSetting uidPs = mSettings.getPackageLPr(uidPackageName); + if (uidPs != null) { + final int index = ArrayUtils.indexOf(uidPs.usesStaticLibraries, + libraryInfo.getName()); + if (index < 0) { + continue; + } + if (uidPs.pkg.getUsesStaticLibrariesVersions()[index] + == libraryInfo.getLongVersion()) { + return false; + } + } + } + return true; + } + + public boolean hasCrossUserPermission( + int callingUid, int callingUserId, int userId, boolean requireFullPermission, + boolean requirePermissionWhenSameUser) { + if (!requirePermissionWhenSameUser && userId == callingUserId) { + return true; + } + if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) { + return true; + } + if (requireFullPermission) { + return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL); + } + return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS); + } + + /** + * @param resolveInfos list of resolve infos in descending priority order + * @return if the list contains a resolve info with non-negative priority + */ + public boolean hasNonNegativePriority(List<ResolveInfo> resolveInfos) { + return resolveInfos.size() > 0 && resolveInfos.get(0).priority >= 0; + } + + public boolean hasPermission(String permission) { + return mContext.checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED; + } + + public boolean isCallerSameApp(String packageName, int uid) { + AndroidPackage pkg = mPackages.get(packageName); + return pkg != null + && UserHandle.getAppId(uid) == pkg.getUid(); + } + + public boolean isComponentVisibleToInstantApp(@Nullable ComponentName component) { + if (isComponentVisibleToInstantApp(component, TYPE_ACTIVITY)) { + return true; + } + if (isComponentVisibleToInstantApp(component, TYPE_SERVICE)) { + return true; + } + if (isComponentVisibleToInstantApp(component, TYPE_PROVIDER)) { + return true; + } + return false; + } + + public boolean isComponentVisibleToInstantApp( + @Nullable ComponentName component, @ComponentType int type) { + if (type == TYPE_ACTIVITY) { + final ParsedActivity activity = mComponentResolver.getActivity(component); + if (activity == null) { + return false; + } + final boolean visibleToInstantApp = + (activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0; + final boolean explicitlyVisibleToInstantApp = + (activity.getFlags() & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) + == 0; + return visibleToInstantApp && explicitlyVisibleToInstantApp; + } else if (type == TYPE_RECEIVER) { + final ParsedActivity activity = mComponentResolver.getReceiver(component); + if (activity == null) { + return false; + } + final boolean visibleToInstantApp = + (activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0; + final boolean explicitlyVisibleToInstantApp = + (activity.getFlags() & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) + == 0; + return visibleToInstantApp && !explicitlyVisibleToInstantApp; + } else if (type == TYPE_SERVICE) { + final ParsedService service = mComponentResolver.getService(component); + return service != null + && (service.getFlags() & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0; + } else if (type == TYPE_PROVIDER) { + final ParsedProvider provider = mComponentResolver.getProvider(component); + return provider != null + && (provider.getFlags() & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0; + } else if (type == TYPE_UNKNOWN) { + return isComponentVisibleToInstantApp(component); + } + return false; + } + + /** + * From Android R, + * camera intents have to match system apps. The only exception to this is if + * the DPC has set the camera persistent preferred activity. This case was introduced + * because it is important that the DPC has the ability to set both system and non-system + * camera persistent preferred activities. + * + * @return {@code true} if the intent is a camera intent and the persistent preferred + * activity was not set by the DPC. + */ + public boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId, + String resolvedType, int flags) { + return intent.isImplicitImageCaptureIntent() && !isPersistentPreferredActivitySetByDpm( + intent, userId, resolvedType, flags); + } + + public boolean isInstantApp(String packageName, int userId) { + final int callingUid = Binder.getCallingUid(); + enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, + false /* checkShell */, "isInstantApp"); + + return isInstantAppInternal(packageName, userId, callingUid); + } + + public boolean isInstantAppInternal(String packageName, @UserIdInt int userId, + int callingUid) { + if (HIDE_EPHEMERAL_APIS) { + return false; + } + return isInstantAppInternalBody(packageName, userId, callingUid); + } + + public boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId, + int callingUid) { + if (Process.isIsolated(callingUid)) { + callingUid = mIsolatedOwners.get(callingUid); + } + final PackageSetting ps = mSettings.getPackageLPr(packageName); + final boolean returnAllowed = + ps != null + && (isCallerSameApp(packageName, callingUid) + || canViewInstantApps(callingUid, userId) + || mInstantAppRegistry.isInstantAccessGranted( + userId, UserHandle.getAppId(callingUid), ps.appId)); + if (returnAllowed) { + return ps.getInstantApp(userId); + } + return false; + } + + public boolean isInstantAppResolutionAllowed( + Intent intent, List<ResolveInfo> resolvedActivities, int userId, + boolean skipPackageCheck) { + if (mInstantAppResolverConnection == null) { + return false; + } + if (instantAppInstallerActivity() == null) { + return false; + } + if (intent.getComponent() != null) { + return false; + } + if ((intent.getFlags() & Intent.FLAG_IGNORE_EPHEMERAL) != 0) { + return false; + } + if (!skipPackageCheck && intent.getPackage() != null) { + return false; + } + if (!intent.isWebIntent()) { + // for non web intents, we should not resolve externally if an app already exists to + // handle it or if the caller didn't explicitly request it. + if ((resolvedActivities != null && resolvedActivities.size() != 0) + || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) == 0) { + return false; + } + } else { + if (intent.getData() == null || TextUtils.isEmpty(intent.getData().getHost())) { + return false; + } else if (areWebInstantAppsDisabled(userId)) { + return false; + } + } + // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. + // Or if there's already an ephemeral app installed that handles the action + return isInstantAppResolutionAllowedBody(intent, resolvedActivities, userId, + skipPackageCheck); + } + + // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. + // Or if there's already an ephemeral app installed that handles the action + public boolean isInstantAppResolutionAllowedBody( + Intent intent, List<ResolveInfo> resolvedActivities, int userId, + boolean skipPackageCheck) { + final int count = (resolvedActivities == null ? 0 : resolvedActivities.size()); + for (int n = 0; n < count; n++) { + final ResolveInfo info = resolvedActivities.get(n); + final String packageName = info.activityInfo.packageName; + final PackageSetting ps = mSettings.getPackageLPr(packageName); + if (ps != null) { + // only check domain verification status if the app is not a browser + if (!info.handleAllWebDataURI) { + // Try to get the status from User settings first + final long packedStatus = getDomainVerificationStatusLPr(ps, userId); + final int status = (int) (packedStatus >> 32); + if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS + || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) { + if (DEBUG_INSTANT) { + Slog.v(TAG, "DENY instant app;" + + " pkg: " + packageName + ", status: " + status); + } + return false; + } + } + if (ps.getInstantApp(userId)) { + if (DEBUG_INSTANT) { + Slog.v(TAG, "DENY instant app installed;" + + " pkg: " + packageName); + } + return false; + } + } + } + // We've exhausted all ways to deny ephemeral application; let the system look for them. + return true; + } + + public boolean isPersistentPreferredActivitySetByDpm(Intent intent, int userId, + String resolvedType, int flags) { + PersistentPreferredIntentResolver ppir = + mSettings.getPersistentPreferredActivities(userId); + //TODO(b/158003772): Remove double query + List<PersistentPreferredActivity> pprefs = ppir != null + ? ppir.queryIntent(intent, resolvedType, + (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, + userId) + : new ArrayList<>(); + for (PersistentPreferredActivity ppa : pprefs) { + if (ppa.mIsSetByDpm) { + return true; + } + } + return false; + } + + public boolean isRecentsAccessingChildProfiles(int callingUid, int targetUserId) { + if (!mInjector.getLocalService(ActivityTaskManagerInternal.class) + .isCallerRecents(callingUid)) { + return false; + } + final long token = Binder.clearCallingIdentity(); + try { + final int callingUserId = UserHandle.getUserId(callingUid); + if (ActivityManager.getCurrentUser() != callingUserId) { + return false; + } + return mUserManager.isSameProfileGroup(callingUserId, targetUserId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) { + final long identity = Binder.clearCallingIdentity(); + try { + return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + public boolean isUserEnabled(int userId) { + final long callingId = Binder.clearCallingIdentity(); + try { + UserInfo userInfo = mUserManager.getUserInfo(userId); + return userInfo != null && userInfo.isEnabled(); + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + + /** + * Returns whether or not access to the application should be filtered. + * <p> + * Access may be limited based upon whether the calling or target applications + * are instant applications. + * + * @see #canViewInstantApps(int, int) + */ + public boolean shouldFilterApplicationLocked(@Nullable PackageSetting ps, int callingUid, + @Nullable ComponentName component, @ComponentType int componentType, int userId) { + // if we're in an isolated process, get the real calling UID + if (Process.isIsolated(callingUid)) { + callingUid = mIsolatedOwners.get(callingUid); + } + final String instantAppPkgName = getInstantAppPackageName(callingUid); + final boolean callerIsInstantApp = instantAppPkgName != null; + if (ps == null) { + if (callerIsInstantApp) { + // pretend the application exists, but, needs to be filtered + return true; + } + return false; + } + // if the target and caller are the same application, don't filter + if (isCallerSameApp(ps.name, callingUid)) { + return false; + } + if (callerIsInstantApp) { + // both caller and target are both instant, but, different applications, filter + if (ps.getInstantApp(userId)) { + return true; + } + // request for a specific component; if it hasn't been explicitly exposed through + // property or instrumentation target, filter + if (component != null) { + final ParsedInstrumentation instrumentation = + mInstrumentation.get(component); + if (instrumentation != null + && isCallerSameApp(instrumentation.getTargetPackage(), callingUid)) { + return false; + } + return !isComponentVisibleToInstantApp(component, componentType); + } + // request for application; if no components have been explicitly exposed, filter + return !ps.pkg.isVisibleToInstantApps(); + } + if (ps.getInstantApp(userId)) { + // caller can see all components of all instant applications, don't filter + if (canViewInstantApps(callingUid, userId)) { + return false; + } + // request for a specific instant application component, filter + if (component != null) { + return true; + } + // request for an instant application; if the caller hasn't been granted access, + //filter + return !mInstantAppRegistry.isInstantAccessGranted( + userId, UserHandle.getAppId(callingUid), ps.appId); + } + int appId = UserHandle.getAppId(callingUid); + final SettingBase callingPs = mSettings.getSettingLPr(appId); + return mAppsFilter.shouldFilterApplication(callingUid, callingPs, ps, userId); + } + + /** + * @see #shouldFilterApplicationLocked(PackageSetting, int, ComponentName, int, int) + */ + public boolean shouldFilterApplicationLocked( + @Nullable PackageSetting ps, int callingUid, int userId) { + return shouldFilterApplicationLocked(ps, callingUid, null, TYPE_UNKNOWN, userId); + } + + /** + * Verification statuses are ordered from the worse to the best, except for + * INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER, which is the worse. + */ + public int bestDomainVerificationStatus(int status1, int status2) { + if (status1 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { + return status2; + } + if (status2 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { + return status1; + } + return (int) MathUtils.max(status1, status2); + } + + // NOTE: Can't remove without a major refactor. Keep around for now. + public int checkUidPermission(String permName, int uid) { + return mPermissionManager.checkUidPermission(uid, permName); + } + + public int getPackageUidInternal(String packageName, int flags, int userId, + int callingUid) { + // reader + final AndroidPackage p = mPackages.get(packageName); + if (p != null && AndroidPackageUtils.isMatchForSystemOnly(p, flags)) { + PackageSetting ps = getPackageSettingInternal(p.getPackageName(), callingUid); + if (shouldFilterApplicationLocked(ps, callingUid, userId)) { + return -1; + } + return UserHandle.getUid(userId, p.getUid()); + } + if ((flags & MATCH_KNOWN_PACKAGES) != 0) { + final PackageSetting ps = mSettings.getPackageLPr(packageName); + if (ps != null && ps.isMatch(flags) + && !shouldFilterApplicationLocked(ps, callingUid, userId)) { + return UserHandle.getUid(userId, ps.appId); + } + } + + return -1; + } + + /** + * Update given flags based on encryption status of current user. + */ + public int updateFlags(int flags, int userId) { + if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE)) != 0) { + // Caller expressed an explicit opinion about what encryption + // aware/unaware components they want to see, so fall through and + // give them what they want + } else { + // Caller expressed no opinion, so match based on user state + if (mUserManager.isUserUnlockingOrUnlocked(userId)) { + flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; + } else { + flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE; + } + } + return flags; + } + + /** + * Update given flags when being used to request {@link ApplicationInfo}. + */ + public int updateFlagsForApplication(int flags, int userId) { + return updateFlagsForPackage(flags, userId); + } + + /** + * Update given flags when being used to request {@link ComponentInfo}. + */ + public int updateFlagsForComponent(int flags, int userId) { + return updateFlags(flags, userId); + } + + /** + * Update given flags when being used to request {@link PackageInfo}. + */ + public int updateFlagsForPackage(int flags, int userId) { + final boolean isCallerSystemUser = UserHandle.getCallingUserId() + == UserHandle.USER_SYSTEM; + if ((flags & PackageManager.MATCH_ANY_USER) != 0) { + // require the permission to be held; the calling uid and given user id referring + // to the same user is not sufficient + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, + !isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId), + "MATCH_ANY_USER flag requires INTERACT_ACROSS_USERS permission"); + } else if ((flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0 + && isCallerSystemUser + && mUserManager.hasManagedProfile(UserHandle.USER_SYSTEM)) { + // If the caller wants all packages and has a restricted profile associated with it, + // then match all users. This is to make sure that launchers that need to access + //work + // profile apps don't start breaking. TODO: Remove this hack when launchers stop + //using + // MATCH_UNINSTALLED_PACKAGES to query apps in other profiles. b/31000380 + flags |= PackageManager.MATCH_ANY_USER; + } + return updateFlags(flags, userId); + } + + /** + * Update given flags when being used to request {@link ResolveInfo}. + * <p>Instant apps are resolved specially, depending upon context. Minimally, + * {@code}flags{@code} must have the {@link PackageManager#MATCH_INSTANT} + * flag set. However, this flag is only honoured in three circumstances: + * <ul> + * <li>when called from a system process</li> + * <li>when the caller holds the permission {@code + * android.permission.ACCESS_INSTANT_APPS}</li> + * <li>when resolution occurs to start an activity with a {@code android.intent.action.VIEW} + * action and a {@code android.intent.category.BROWSABLE} category</li> + * </ul> + */ + public int updateFlagsForResolve(int flags, int userId, int callingUid, + boolean wantInstantApps, boolean isImplicitImageCaptureIntentAndNotSetByDpc) { + return updateFlagsForResolve(flags, userId, callingUid, + wantInstantApps, false /*onlyExposedExplicitly*/, + isImplicitImageCaptureIntentAndNotSetByDpc); + } + + public int updateFlagsForResolve(int flags, int userId, int callingUid, + boolean wantInstantApps, boolean onlyExposedExplicitly, + boolean isImplicitImageCaptureIntentAndNotSetByDpc) { + // Safe mode means we shouldn't match any third-party components + if (safeMode() || isImplicitImageCaptureIntentAndNotSetByDpc) { + flags |= PackageManager.MATCH_SYSTEM_ONLY; + } + if (getInstantAppPackageName(callingUid) != null) { + // But, ephemeral apps see both ephemeral and exposed, non-ephemeral components + if (onlyExposedExplicitly) { + flags |= PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY; + } + flags |= PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY; + flags |= PackageManager.MATCH_INSTANT; + } else { + final boolean wantMatchInstant = (flags & PackageManager.MATCH_INSTANT) != 0; + final boolean allowMatchInstant = wantInstantApps + || (wantMatchInstant && canViewInstantApps(callingUid, userId)); + flags &= ~(PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY + | PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY); + if (!allowMatchInstant) { + flags &= ~PackageManager.MATCH_INSTANT; + } + } + return updateFlagsForComponent(flags, userId); + } + + // Returns a packed value as a long: + // + // high 'int'-sized word: link status: undefined/ask/never/always. + // low 'int'-sized word: relative priority among 'always' results. + public long getDomainVerificationStatusLPr(PackageSetting ps, int userId) { + long result = ps.getDomainVerificationStatusForUser(userId); + // if none available, get the status + if (result >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) { + if (ps.getIntentFilterVerificationInfo() != null) { + result = ((long)ps.getIntentFilterVerificationInfo().getStatus()) << 32; + } + } + return result; + } + + /** + * Checks if the request is from the system or an app that has the appropriate cross-user + * permissions defined as follows: + * <ul> + * <li>INTERACT_ACROSS_USERS_FULL if {@code requireFullPermission} is true.</li> + * <li>INTERACT_ACROSS_USERS if the given {@code userId} is in a different profile group + * to the caller.</li> + * <li>Otherwise, + * INTERACT_ACROSS_PROFILES if the given {@code userId} is in the same profile + * group as the caller.</li> + * </ul> + * + * @param checkShell whether to prevent shell from access if there's a debugging restriction + * @param message the message to log on security exception + */ + public void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId, + boolean requireFullPermission, boolean checkShell, String message) { + if (userId < 0) { + throw new IllegalArgumentException("Invalid userId " + userId); + } + if (checkShell) { + PackageManagerServiceUtils.enforceShellRestriction( + mInjector.getUserManagerInternal(), + UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); + } + final int callingUserId = UserHandle.getUserId(callingUid); + if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission, + /*requirePermissionWhenSameUser= */ false)) { + return; + } + final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId); + if (isSameProfileGroup && PermissionChecker.checkPermissionForPreflight( + mContext, + android.Manifest.permission.INTERACT_ACROSS_PROFILES, + PermissionChecker.PID_UNKNOWN, + callingUid, + getPackage(callingUid).getPackageName()) + == PermissionChecker.PERMISSION_GRANTED) { + return; + } + String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage( + callingUid, userId, message, requireFullPermission, isSameProfileGroup); + Slog.w(TAG, errorMessage); + throw new SecurityException(errorMessage); + } + + /** + * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS + * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userId} is not for the caller. + * + * @param checkShell whether to prevent shell from access if there's a debugging restriction + * @param message the message to log on security exception + */ + public void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, + boolean requireFullPermission, boolean checkShell, String message) { + enforceCrossUserPermission(callingUid, userId, requireFullPermission, checkShell, false, + message); + } + + /** + * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS + * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userId} is not for the caller. + * + * @param checkShell whether to prevent shell from access if there's a debugging restriction + * @param requirePermissionWhenSameUser When {@code true}, still require the cross user + * permission to be held even if the callingUid and + * userId + * reference the same user. + * @param message the message to log on security exception + */ + public void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, + boolean requireFullPermission, boolean checkShell, + boolean requirePermissionWhenSameUser, String message) { + if (userId < 0) { + throw new IllegalArgumentException("Invalid userId " + userId); + } + if (checkShell) { + PackageManagerServiceUtils.enforceShellRestriction( + mInjector.getUserManagerInternal(), + UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); + } + final int callingUserId = UserHandle.getUserId(callingUid); + if (hasCrossUserPermission( + callingUid, callingUserId, userId, requireFullPermission, + requirePermissionWhenSameUser)) { + return; + } + String errorMessage = buildInvalidCrossUserPermissionMessage( + callingUid, userId, message, requireFullPermission); + Slog.w(TAG, errorMessage); + throw new SecurityException(errorMessage); + } + + } + + /** + * The live computer differs from the ComputerEngine in the methods that fetch data + * from PackageManagerService. + **/ + private static class ComputerEngineLive extends ComputerEngine { + ComputerEngineLive(Snapshot args) { + super(args); + } + protected ComponentName resolveComponentName() { + return mService.mResolveComponentName; + } + protected ActivityInfo instantAppInstallerActivity() { + return mService.mInstantAppInstallerActivity; + } + protected ApplicationInfo androidApplication() { + return mService.mAndroidApplication; + } + } + + /** + * This subclass is the external interface to the live computer. For each + * interface, it takes the PM lock and then delegates to the live + * computer engine. This is required because there are no locks taken in + * the engine itself. + */ + private static class ComputerLocked extends ComputerEngine { + private final Object mLock; + + ComputerLocked(Snapshot args) { + super(args); + mLock = mService.mLock; + } + + protected ComponentName resolveComponentName() { + return mService.mResolveComponentName; + } + protected ActivityInfo instantAppInstallerActivity() { + return mService.mInstantAppInstallerActivity; + } + protected ApplicationInfo androidApplication() { + return mService.mAndroidApplication; + } + + public @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent, + String resolvedType, int flags, int userId, int callingUid, + String instantAppPkgName) { + synchronized (mLock) { + return super.queryIntentServicesInternalBody(intent, resolvedType, flags, userId, + callingUid, instantAppPkgName); + } + } + public @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(Intent intent, + String resolvedType, int flags, int filterCallingUid, int userId, + boolean resolveForStart, boolean allowDynamicSplits, String pkgName, + String instantAppPkgName) { + synchronized (mLock) { + return super.queryIntentActivitiesInternalBody(intent, resolvedType, flags, + filterCallingUid, userId, resolveForStart, allowDynamicSplits, pkgName, + instantAppPkgName); + } + } + public ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags, + int filterCallingUid, int userId) { + synchronized (mLock) { + return super.getActivityInfoInternalBody(component, flags, filterCallingUid, + userId); + } + } + public AndroidPackage getPackage(String packageName) { + synchronized (mLock) { + return super.getPackage(packageName); + } + } + public AndroidPackage getPackage(int uid) { + synchronized (mLock) { + return super.getPackage(uid); + } + } + public ApplicationInfo getApplicationInfoInternalBody(String packageName, int flags, + int filterCallingUid, int userId) { + synchronized (mLock) { + return super.getApplicationInfoInternalBody(packageName, flags, filterCallingUid, + userId); + } + } + public ArrayList<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPrBody( + Intent intent, int matchFlags, List<ResolveInfo> candidates, + CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) { + synchronized (mLock) { + return super.filterCandidatesWithDomainPreferredActivitiesLPrBody(intent, + matchFlags, candidates, xpDomainInfo, userId, debug); + } + } + public PackageInfo getPackageInfoInternalBody(String packageName, long versionCode, + int flags, int filterCallingUid, int userId) { + synchronized (mLock) { + return super.getPackageInfoInternalBody(packageName, versionCode, flags, + filterCallingUid, userId); + } + } + public PackageSetting getPackageSettingInternal(String packageName, int callingUid) { + synchronized (mLock) { + return super.getPackageSettingInternal(packageName, callingUid); + } + } + public ParceledListSlice<PackageInfo> getInstalledPackagesBody(int flags, int userId, + int callingUid) { + synchronized (mLock) { + return super.getInstalledPackagesBody(flags, userId, callingUid); + } + } + public ServiceInfo getServiceInfoBody(ComponentName component, int flags, int userId, + int callingUid) { + synchronized (mLock) { + return super.getServiceInfoBody(component, flags, userId, callingUid); + } + } + public String getInstantAppPackageName(int callingUid) { + synchronized (mLock) { + return super.getInstantAppPackageName(callingUid); + } + } + public String[] getPackagesForUidInternalBody(int callingUid, int userId, int appId, + boolean isCallerInstantApp) { + synchronized (mLock) { + return super.getPackagesForUidInternalBody(callingUid, userId, appId, + isCallerInstantApp); + } + } + public boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId, + int callingUid) { + synchronized (mLock) { + return super.isInstantAppInternalBody(packageName, userId, callingUid); + } + } + public boolean isInstantAppResolutionAllowedBody(Intent intent, + List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck) { + synchronized (mLock) { + return super.isInstantAppResolutionAllowedBody(intent, resolvedActivities, userId, + skipPackageCheck); + } + } + public int getPackageUidInternal(String packageName, int flags, int userId, + int callingUid) { + synchronized (mLock) { + return super.getPackageUidInternal(packageName, flags, userId, callingUid); + } + } + } + + + // Compute read-only functions, based on live data. + private final Computer mLiveComputer; + // A lock-free cache for frequently called functions. + private volatile Computer mSnapshotComputer; + // If true, the cached computer object is invalid (the cache is stale). + // The attribute is static since it may be set from outside classes. + private static volatile boolean sSnapshotInvalid = true; + // If true, the cache is corked. Do not create a new cache but continue to use the + // existing one. This throttles cache creation during periods of churn in Package + // Manager. + private static volatile boolean sSnapshotCorked = false; + + // A counter of all queries that hit the cache. + private AtomicInteger mSnapshotHits = new AtomicInteger(0); + + // The number of queries at the last miss. This is updated when the cache is rebuilt + // (guarded by mLock) and is used to report the hit run-length. + @GuardedBy("mLock") + private int mSnapshotRebuilt = 0; + + // The snapshot disable/enable switch. An image with the flag set true uses snapshots + // and an image with the flag set false does not use snapshots. + private static final boolean SNAPSHOT_ENABLED = true; + + /** + * Return the live or cached computer. The method will rebuild the + * cached computer if necessary. + */ + private Computer computer(boolean live) { + if (live || !SNAPSHOT_ENABLED) { + return mLiveComputer; + } else { + int hits = 0; + if (TRACE_CACHES) { + hits = mSnapshotHits.incrementAndGet(); + } + Computer c = mSnapshotComputer; + if ((sSnapshotInvalid || (c == null)) && !sSnapshotCorked) { + synchronized (mLock) { + // Rebuild the computer if it is invalid and if the cache is not + // corked. The lock is taken inside the rebuild method. Note that + // the cache might be invalidated as it is rebuilt. However, the + // cache is still consistent and is current as of the time this + // function is entered. + if (sSnapshotInvalid) { + rebuildSnapshot(hits); + } + // Guaranteed to be non-null + c = mSnapshotComputer; + } + } + return c; + } + } + + /** + * Return the live computer if the thread holds the lock, and the cached + * computer otehrwise. This method is for functions that are unsure + * which computer to use. + **/ + private Computer computer() { + return computer(Thread.holdsLock(mLock)); + } + + /** + * Rebuild the cached computer. + */ + @GuardedBy("mLock") + private void rebuildSnapshot(int hits) { + mSnapshotComputer = null; + sSnapshotInvalid = false; + final Snapshot args = new Snapshot(Snapshot.SNAPPED); + mSnapshotComputer = new ComputerEngine(args); + + // Still guarded by mLock + final int run = hits - mSnapshotRebuilt; + mSnapshotRebuilt = hits; + if (TRACE_CACHES) { + Log.w(TAG, "computer: rebuild after " + run + " hits"); + } + } + + /** + * Create a live computer + */ + private ComputerLocked liveComputer() { + return new ComputerLocked(new Snapshot(Snapshot.LIVE)); + } + + /** + * This method is called when the state of PackageManagerService changes so as to + * invalidate the current snapshot. + * @param what The {@link Watchable} that reported the change + * @hide + */ + public static void onChange(@Nullable Watchable what) { + if (TRACE_CACHES) { + Log.e(TAG, "computer: onChange(" + what + ")"); + } + sSnapshotInvalid = true; + } + + /** + * Report a locally-detected change to observers. The <what> parameter is left null, + * but it signifies that the change was detected by PackageManagerService itself. + */ + private static void onChanged() { + onChange(null); } class PackageHandler extends Handler { @@ -3093,6 +6047,25 @@ public class PackageManagerService extends IPackageManager.Stub mSharedSystemSharedLibraryPackageName = testParams.sharedSystemSharedLibraryPackageName; mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage; mResolveComponentName = testParams.resolveComponentName; + + // Create the computer as soon as the state objects have been installed. The + // cached computer is the same as the live computer until the end of the + // constructor, at which time the invalidation method updates it. The cache is + // corked initially to ensure a cached computer is not built until the end of the + // constructor. + sSnapshotCorked = true; + mLiveComputer = liveComputer(); + mSnapshotComputer = mLiveComputer; + + // Link up the watchers + mPackages.registerObserver(mWatcher); + mSharedLibraries.registerObserver(mWatcher); + mStaticLibsByDeclaringPackage.registerObserver(mWatcher); + mInstrumentation.registerObserver(mWatcher); + mWebInstantAppsDisabled.registerObserver(mWatcher); + mAppsFilter.registerObserver(mWatcher); + Watchable.verifyWatchedAttributes(this, mWatcher); + mPackages.putAll(testParams.packages); mEnableFreeCacheV2 = testParams.enableFreeCacheV2; mSdkVersion = testParams.sdkVersion; @@ -3102,6 +6075,9 @@ public class PackageManagerService extends IPackageManager.Stub mIsEngBuild = testParams.isEngBuild; mIsUserDebugBuild = testParams.isUserDebugBuild; mIncrementalVersion = testParams.incrementalVersion; + + invalidatePackageInfoCache(); + sSnapshotCorked = false; } public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest, @@ -3229,6 +6205,8 @@ public class PackageManagerService extends IPackageManager.Stub } } + mInstantAppRegistry = new InstantAppRegistry(this, mPermissionManager); + mDirsToScanAsSystem = new ArrayList<>(); mDirsToScanAsSystem.addAll(injector.getSystemPartitions()); mDirsToScanAsSystem.addAll(scanPartitions); @@ -3237,6 +6215,24 @@ public class PackageManagerService extends IPackageManager.Stub mAppInstallDir = new File(Environment.getDataDirectory(), "app"); mAppLib32InstallDir = getAppLib32InstallDir(); + // Link up the watchers + mPackages.registerObserver(mWatcher); + mSharedLibraries.registerObserver(mWatcher); + mStaticLibsByDeclaringPackage.registerObserver(mWatcher); + mInstrumentation.registerObserver(mWatcher); + mWebInstantAppsDisabled.registerObserver(mWatcher); + mAppsFilter.registerObserver(mWatcher); + Watchable.verifyWatchedAttributes(this, mWatcher); + + // Create the computer as soon as the state objects have been installed. The + // cached computer is the same as the live computer until the end of the + // constructor, at which time the invalidation method updates it. The cache is + // corked initially to ensure a cached computer is not built until the end of the + // constructor. + sSnapshotCorked = true; + mLiveComputer = liveComputer(); + mSnapshotComputer = mLiveComputer; + // CHECKSTYLE:OFF IndentationCheck synchronized (mInstallLock) { // writer @@ -3247,7 +6243,6 @@ public class PackageManagerService extends IPackageManager.Stub mHandler = new PackageHandler(handlerThread.getLooper()); mProcessLoggingHandler = new ProcessLoggingHandler(); Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT); - mInstantAppRegistry = new InstantAppRegistry(this, mPermissionManager); ArrayMap<String, SystemConfig.SharedLibraryEntry> libConfig = systemConfig.getSharedLibraries(); @@ -4616,96 +7611,11 @@ public class PackageManagerService extends IPackageManager.Stub * </ol> */ private boolean canViewInstantApps(int callingUid, int userId) { - if (callingUid < Process.FIRST_APPLICATION_UID) { - return true; - } - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.ACCESS_INSTANT_APPS) == PERMISSION_GRANTED) { - return true; - } - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.VIEW_INSTANT_APPS) == PERMISSION_GRANTED) { - final ComponentName homeComponent = getDefaultHomeActivity(userId); - if (homeComponent != null - && isCallerSameApp(homeComponent.getPackageName(), callingUid)) { - return true; - } - // TODO(b/122900055) Change/Remove this and replace with new permission role. - if (mAppPredictionServicePackage != null - && isCallerSameApp(mAppPredictionServicePackage, callingUid)) { - return true; - } - } - return false; + return computer(true).canViewInstantApps(callingUid, userId); } private PackageInfo generatePackageInfo(PackageSetting ps, int flags, int userId) { - if (!mUserManager.exists(userId)) return null; - if (ps == null) { - return null; - } - final int callingUid = Binder.getCallingUid(); - // Filter out ephemeral app metadata: - // * The system/shell/root can see metadata for any app - // * An installed app can see metadata for 1) other installed apps - // and 2) ephemeral apps that have explicitly interacted with it - // * Ephemeral apps can only see their own data and exposed installed apps - // * Holding a signature permission allows seeing instant apps - if (shouldFilterApplicationLocked(ps, callingUid, userId)) { - return null; - } - - if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0 - && ps.isSystem()) { - flags |= MATCH_ANY_USER; - } - - final PackageUserState state = ps.readUserState(userId); - AndroidPackage p = ps.pkg; - if (p != null) { - // Compute GIDs only if requested - final int[] gids = (flags & PackageManager.GET_GIDS) == 0 ? EMPTY_INT_ARRAY - : mPermissionManager.getGidsForUid(UserHandle.getUid(userId, ps.appId)); - // Compute granted permissions only if package has requested permissions - final Set<String> permissions = ArrayUtils.isEmpty(p.getRequestedPermissions()) - ? Collections.emptySet() - : mPermissionManager.getGrantedPermissions(ps.name, userId); - - PackageInfo packageInfo = PackageInfoUtils.generate(p, gids, flags, - ps.firstInstallTime, ps.lastUpdateTime, permissions, state, userId, ps); - - if (packageInfo == null) { - return null; - } - - packageInfo.packageName = packageInfo.applicationInfo.packageName = - resolveExternalPackageNameLPr(p); - - return packageInfo; - } else if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0 && state.isAvailable(flags)) { - PackageInfo pi = new PackageInfo(); - pi.packageName = ps.name; - pi.setLongVersionCode(ps.versionCode); - pi.sharedUserId = (ps.sharedUser != null) ? ps.sharedUser.name : null; - pi.firstInstallTime = ps.firstInstallTime; - pi.lastUpdateTime = ps.lastUpdateTime; - - ApplicationInfo ai = new ApplicationInfo(); - ai.packageName = ps.name; - ai.uid = UserHandle.getUid(userId, ps.appId); - ai.primaryCpuAbi = ps.primaryCpuAbiString; - ai.secondaryCpuAbi = ps.secondaryCpuAbiString; - ai.setVersionCode(ps.versionCode); - ai.flags = ps.pkgFlags; - ai.privateFlags = ps.pkgPrivateFlags; - pi.applicationInfo = PackageParser.generateApplicationInfo(ai, flags, state, userId); - - if (DEBUG_PACKAGE_INFO) Log.v(TAG, "ps.pkg is n/a for [" - + ps.name + "]. Provides a minimum info."); - return pi; - } else { - return null; - } + return computer(true).generatePackageInfo(ps, flags, userId); } @Override @@ -4770,8 +7680,8 @@ public class PackageManagerService extends IPackageManager.Stub @Override public PackageInfo getPackageInfo(String packageName, int flags, int userId) { - return getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST, - flags, Binder.getCallingUid(), userId); + // SNAPSHOT + return computer(false).getPackageInfo(packageName, flags, userId); } @Override @@ -4784,128 +7694,29 @@ public class PackageManagerService extends IPackageManager.Stub /** * Important: The provided filterCallingUid is used exclusively to filter out packages * that can be seen based on user state. It's typically the original caller uid prior - * to clearing. Because it can only be provided by trusted code, it's value can be + * to clearing. Because it can only be provided by trusted code, its value can be * trusted and will be used as-is; unlike userId which will be validated by this method. */ private PackageInfo getPackageInfoInternal(String packageName, long versionCode, int flags, int filterCallingUid, int userId) { - if (!mUserManager.exists(userId)) return null; - flags = updateFlagsForPackage(flags, userId); - enforceCrossUserPermission(Binder.getCallingUid(), userId, - false /* requireFullPermission */, false /* checkShell */, "get package info"); - - return getPackageInfoInternalBody(packageName, versionCode, flags, filterCallingUid, - userId); + return computer(true).getPackageInfoInternal(packageName, versionCode, + flags, filterCallingUid, userId); } private PackageInfo getPackageInfoInternalBody(String packageName, long versionCode, int flags, int filterCallingUid, int userId) { - // reader - synchronized (mLock) { - // Normalize package name to handle renamed packages and static libs - packageName = resolveInternalPackageNameLPr(packageName, versionCode); - - final boolean matchFactoryOnly = (flags & MATCH_FACTORY_ONLY) != 0; - if (matchFactoryOnly) { - // Instant app filtering for APEX modules is ignored - if ((flags & MATCH_APEX) != 0) { - return mApexManager.getPackageInfo(packageName, - ApexManager.MATCH_FACTORY_PACKAGE); - } - final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName); - if (ps != null) { - if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) { - return null; - } - if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) { - return null; - } - return generatePackageInfo(ps, flags, userId); - } - } - - AndroidPackage p = mPackages.get(packageName); - if (matchFactoryOnly && p != null && !p.isSystem()) { - return null; - } - if (DEBUG_PACKAGE_INFO) - Log.v(TAG, "getPackageInfo " + packageName + ": " + p); - if (p != null) { - final PackageSetting ps = getPackageSetting(p.getPackageName()); - if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) { - return null; - } - if (ps != null && shouldFilterApplicationLocked(ps, filterCallingUid, userId)) { - return null; - } - - return generatePackageInfo(ps, flags, userId); - } - if (!matchFactoryOnly && (flags & MATCH_KNOWN_PACKAGES) != 0) { - final PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps == null) return null; - if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) { - return null; - } - if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) { - return null; - } - return generatePackageInfo(ps, flags, userId); - } - if ((flags & MATCH_APEX) != 0) { - return mApexManager.getPackageInfo(packageName, ApexManager.MATCH_ACTIVE_PACKAGE); - } - } - return null; + return computer(true).getPackageInfoInternalBody(packageName, versionCode, + flags, filterCallingUid, userId); } private boolean isComponentVisibleToInstantApp(@Nullable ComponentName component) { - if (isComponentVisibleToInstantApp(component, TYPE_ACTIVITY)) { - return true; - } - if (isComponentVisibleToInstantApp(component, TYPE_SERVICE)) { - return true; - } - if (isComponentVisibleToInstantApp(component, TYPE_PROVIDER)) { - return true; - } - return false; + return computer(true).isComponentVisibleToInstantApp(component); } private boolean isComponentVisibleToInstantApp( @Nullable ComponentName component, @ComponentType int type) { - if (type == TYPE_ACTIVITY) { - final ParsedActivity activity = mComponentResolver.getActivity(component); - if (activity == null) { - return false; - } - final boolean visibleToInstantApp = - (activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0; - final boolean explicitlyVisibleToInstantApp = - (activity.getFlags() & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0; - return visibleToInstantApp && explicitlyVisibleToInstantApp; - } else if (type == TYPE_RECEIVER) { - final ParsedActivity activity = mComponentResolver.getReceiver(component); - if (activity == null) { - return false; - } - final boolean visibleToInstantApp = - (activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0; - final boolean explicitlyVisibleToInstantApp = - (activity.getFlags() & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0; - return visibleToInstantApp && !explicitlyVisibleToInstantApp; - } else if (type == TYPE_SERVICE) { - final ParsedService service = mComponentResolver.getService(component); - return service != null - && (service.getFlags() & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0; - } else if (type == TYPE_PROVIDER) { - final ParsedProvider provider = mComponentResolver.getProvider(component); - return provider != null - && (provider.getFlags() & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0; - } else if (type == TYPE_UNKNOWN) { - return isComponentVisibleToInstantApp(component); - } - return false; + return computer(true).isComponentVisibleToInstantApp( + component, type); } /** @@ -4919,58 +7730,8 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mLock") private boolean shouldFilterApplicationLocked(@Nullable PackageSetting ps, int callingUid, @Nullable ComponentName component, @ComponentType int componentType, int userId) { - // if we're in an isolated process, get the real calling UID - if (Process.isIsolated(callingUid)) { - callingUid = mIsolatedOwners.get(callingUid); - } - final String instantAppPkgName = getInstantAppPackageName(callingUid); - final boolean callerIsInstantApp = instantAppPkgName != null; - if (ps == null) { - if (callerIsInstantApp) { - // pretend the application exists, but, needs to be filtered - return true; - } - return false; - } - // if the target and caller are the same application, don't filter - if (isCallerSameApp(ps.name, callingUid)) { - return false; - } - if (callerIsInstantApp) { - // both caller and target are both instant, but, different applications, filter - if (ps.getInstantApp(userId)) { - return true; - } - // request for a specific component; if it hasn't been explicitly exposed through - // property or instrumentation target, filter - if (component != null) { - final ParsedInstrumentation instrumentation = - mInstrumentation.get(component); - if (instrumentation != null - && isCallerSameApp(instrumentation.getTargetPackage(), callingUid)) { - return false; - } - return !isComponentVisibleToInstantApp(component, componentType); - } - // request for application; if no components have been explicitly exposed, filter - return !ps.pkg.isVisibleToInstantApps(); - } - if (ps.getInstantApp(userId)) { - // caller can see all components of all instant applications, don't filter - if (canViewInstantApps(callingUid, userId)) { - return false; - } - // request for a specific instant application component, filter - if (component != null) { - return true; - } - // request for an instant application; if the caller hasn't been granted access, filter - return !mInstantAppRegistry.isInstantAccessGranted( - userId, UserHandle.getAppId(callingUid), ps.appId); - } - int appId = UserHandle.getAppId(callingUid); - final SettingBase callingPs = mSettings.getSettingLPr(appId); - return mAppsFilter.shouldFilterApplication(callingUid, callingPs, ps, userId); + return computer(true).shouldFilterApplicationLocked(ps, callingUid, + component, componentType, userId); } /** @@ -4979,63 +7740,15 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mLock") private boolean shouldFilterApplicationLocked( @Nullable PackageSetting ps, int callingUid, int userId) { - return shouldFilterApplicationLocked(ps, callingUid, null, TYPE_UNKNOWN, userId); + return computer(true).shouldFilterApplicationLocked( + ps, callingUid, userId); } @GuardedBy("mLock") private boolean filterSharedLibPackageLPr(@Nullable PackageSetting ps, int uid, int userId, int flags) { - // Callers can access only the libs they depend on, otherwise they need to explicitly - // ask for the shared libraries given the caller is allowed to access all static libs. - if ((flags & PackageManager.MATCH_STATIC_SHARED_LIBRARIES) != 0) { - // System/shell/root get to see all static libs - final int appId = UserHandle.getAppId(uid); - if (appId == Process.SYSTEM_UID || appId == Process.SHELL_UID - || appId == Process.ROOT_UID) { - return false; - } - // Installer gets to see all static libs. - if (PackageManager.PERMISSION_GRANTED - == checkUidPermission(Manifest.permission.INSTALL_PACKAGES, uid)) { - return false; - } - } - - // No package means no static lib as it is always on internal storage - if (ps == null || ps.pkg == null || !ps.pkg.isStaticSharedLibrary()) { - return false; - } - - final SharedLibraryInfo libraryInfo = getSharedLibraryInfoLPr( - ps.pkg.getStaticSharedLibName(), ps.pkg.getStaticSharedLibVersion()); - if (libraryInfo == null) { - return false; - } - - final int resolvedUid = UserHandle.getUid(userId, UserHandle.getAppId(uid)); - final String[] uidPackageNames = getPackagesForUid(resolvedUid); - if (uidPackageNames == null) { - return true; - } - - for (String uidPackageName : uidPackageNames) { - if (ps.name.equals(uidPackageName)) { - return false; - } - PackageSetting uidPs = mSettings.getPackageLPr(uidPackageName); - if (uidPs != null) { - final int index = ArrayUtils.indexOf(uidPs.usesStaticLibraries, - libraryInfo.getName()); - if (index < 0) { - continue; - } - if (uidPs.pkg.getUsesStaticLibrariesVersions()[index] - == libraryInfo.getLongVersion()) { - return false; - } - } - } - return true; + return computer(true).filterSharedLibPackageLPr(ps, uid, userId, + flags); } @Override @@ -5105,26 +7818,7 @@ public class PackageManagerService extends IPackageManager.Stub } private int getPackageUidInternal(String packageName, int flags, int userId, int callingUid) { - // reader - synchronized (mLock) { - final AndroidPackage p = mPackages.get(packageName); - if (p != null && AndroidPackageUtils.isMatchForSystemOnly(p, flags)) { - PackageSetting ps = getPackageSettingInternal(p.getPackageName(), callingUid); - if (shouldFilterApplicationLocked(ps, callingUid, userId)) { - return -1; - } - return UserHandle.getUid(userId, p.getUid()); - } - if ((flags & MATCH_KNOWN_PACKAGES) != 0) { - final PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps != null && ps.isMatch(flags) - && !shouldFilterApplicationLocked(ps, callingUid, userId)) { - return UserHandle.getUid(userId, ps.appId); - } - } - } - - return -1; + return computer(true).getPackageUidInternal(packageName, flags, userId, callingUid); } @Override @@ -5171,111 +7865,32 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mLock") private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags, int filterCallingUid, int userId) { - if (!mUserManager.exists(userId)) return null; - PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps != null) { - if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) { - return null; - } - if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) { - return null; - } - if (ps.pkg == null) { - final PackageInfo pInfo = generatePackageInfo(ps, flags, userId); - if (pInfo != null) { - return pInfo.applicationInfo; - } - return null; - } - ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(ps.pkg, flags, - ps.readUserState(userId), userId, ps); - if (ai != null) { - ai.packageName = resolveExternalPackageNameLPr(ps.pkg); - } - return ai; - } - return null; + return computer(true).generateApplicationInfoFromSettingsLPw(packageName, flags, + filterCallingUid, userId); } @Override public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) { - return getApplicationInfoInternal(packageName, flags, Binder.getCallingUid(), userId); + // SNAPSHOT + return computer(false).getApplicationInfo(packageName, flags, userId); } /** * Important: The provided filterCallingUid is used exclusively to filter out applications * that can be seen based on user state. It's typically the original caller uid prior - * to clearing. Because it can only be provided by trusted code, it's value can be + * to clearing. Because it can only be provided by trusted code, its value can be * trusted and will be used as-is; unlike userId which will be validated by this method. */ private ApplicationInfo getApplicationInfoInternal(String packageName, int flags, int filterCallingUid, int userId) { - if (!mUserManager.exists(userId)) return null; - flags = updateFlagsForApplication(flags, userId); - - if (!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId)) { - enforceCrossUserPermission(Binder.getCallingUid(), userId, - false /* requireFullPermission */, false /* checkShell */, - "get application info"); - } - - return getApplicationInfoInternalBody(packageName, flags, filterCallingUid, userId); + return computer(true).getApplicationInfoInternal(packageName, flags, + filterCallingUid, userId); } private ApplicationInfo getApplicationInfoInternalBody(String packageName, int flags, int filterCallingUid, int userId) { - // writer - synchronized (mLock) { - // Normalize package name to handle renamed packages and static libs - packageName = resolveInternalPackageNameLPr(packageName, - PackageManager.VERSION_CODE_HIGHEST); - - AndroidPackage p = mPackages.get(packageName); - if (DEBUG_PACKAGE_INFO) Log.v( - TAG, "getApplicationInfo " + packageName - + ": " + p); - if (p != null) { - PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps == null) return null; - if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) { - return null; - } - if (shouldFilterApplicationLocked(ps, filterCallingUid, userId)) { - return null; - } - // Note: isEnabledLP() does not apply here - always return info - ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo( - p, flags, ps.readUserState(userId), userId, ps); - if (ai != null) { - ai.packageName = resolveExternalPackageNameLPr(p); - } - return ai; - } - if ((flags & PackageManager.MATCH_APEX) != 0) { - // For APKs, PackageInfo.applicationInfo is not exactly the same as ApplicationInfo - // returned from getApplicationInfo, but for APEX packages difference shouldn't be - // very big. - // TODO(b/155328545): generate proper application info for APEXes as well. - int apexFlags = ApexManager.MATCH_ACTIVE_PACKAGE; - if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) { - apexFlags = ApexManager.MATCH_FACTORY_PACKAGE; - } - final PackageInfo pi = mApexManager.getPackageInfo(packageName, apexFlags); - if (pi == null) { - return null; - } - return pi.applicationInfo; - } - if ("android".equals(packageName)||"system".equals(packageName)) { - return mAndroidApplication; - } - if ((flags & MATCH_KNOWN_PACKAGES) != 0) { - // Already generates the external package name - return generateApplicationInfoFromSettingsLPw(packageName, - flags, filterCallingUid, userId); - } - } - return null; + return computer(true).getApplicationInfoInternalBody(packageName, flags, + filterCallingUid, userId); } @GuardedBy("mLock") @@ -5507,56 +8122,28 @@ public class PackageManagerService extends IPackageManager.Stub * Update given flags based on encryption status of current user. */ private int updateFlags(int flags, int userId) { - if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE)) != 0) { - // Caller expressed an explicit opinion about what encryption - // aware/unaware components they want to see, so fall through and - // give them what they want - } else { - // Caller expressed no opinion, so match based on user state - if (mUserManager.isUserUnlockingOrUnlocked(userId)) { - flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; - } else { - flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE; - } - } - return flags; + return computer(true).updateFlags(flags, userId); } /** * Update given flags when being used to request {@link PackageInfo}. */ private int updateFlagsForPackage(int flags, int userId) { - final boolean isCallerSystemUser = UserHandle.getCallingUserId() == UserHandle.USER_SYSTEM; - if ((flags & PackageManager.MATCH_ANY_USER) != 0) { - // require the permission to be held; the calling uid and given user id referring - // to the same user is not sufficient - enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, - !isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId), - "MATCH_ANY_USER flag requires INTERACT_ACROSS_USERS permission"); - } else if ((flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0 && isCallerSystemUser - && mUserManager.hasManagedProfile(UserHandle.USER_SYSTEM)) { - // If the caller wants all packages and has a restricted profile associated with it, - // then match all users. This is to make sure that launchers that need to access work - // profile apps don't start breaking. TODO: Remove this hack when launchers stop using - // MATCH_UNINSTALLED_PACKAGES to query apps in other profiles. b/31000380 - flags |= PackageManager.MATCH_ANY_USER; - } - return updateFlags(flags, userId); + return computer(true).updateFlagsForPackage(flags, userId); } /** * Update given flags when being used to request {@link ApplicationInfo}. */ private int updateFlagsForApplication(int flags, int userId) { - return updateFlagsForPackage(flags, userId); + return computer(true).updateFlagsForApplication(flags, userId); } /** * Update given flags when being used to request {@link ComponentInfo}. */ private int updateFlagsForComponent(int flags, int userId) { - return updateFlags(flags, userId); + return computer(true).updateFlagsForComponent(flags, userId); } /** @@ -5584,38 +8171,18 @@ public class PackageManagerService extends IPackageManager.Stub * action and a {@code android.intent.category.BROWSABLE} category</li> * </ul> */ - int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, - boolean isImplicitImageCaptureIntentAndNotSetByDpc) { - return updateFlagsForResolve(flags, userId, callingUid, - wantInstantApps, false /*onlyExposedExplicitly*/, - isImplicitImageCaptureIntentAndNotSetByDpc); + private int updateFlagsForResolve(int flags, int userId, int callingUid, + boolean wantInstantApps, boolean isImplicitImageCaptureIntentAndNotSetByDpc) { + return computer(true).updateFlagsForResolve(flags, userId, callingUid, + wantInstantApps, isImplicitImageCaptureIntentAndNotSetByDpc); } - int updateFlagsForResolve(int flags, int userId, int callingUid, + private int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, boolean onlyExposedExplicitly, boolean isImplicitImageCaptureIntentAndNotSetByDpc) { - // Safe mode means we shouldn't match any third-party components - if (mSafeMode || isImplicitImageCaptureIntentAndNotSetByDpc) { - flags |= PackageManager.MATCH_SYSTEM_ONLY; - } - if (getInstantAppPackageName(callingUid) != null) { - // But, ephemeral apps see both ephemeral and exposed, non-ephemeral components - if (onlyExposedExplicitly) { - flags |= PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY; - } - flags |= PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY; - flags |= PackageManager.MATCH_INSTANT; - } else { - final boolean wantMatchInstant = (flags & PackageManager.MATCH_INSTANT) != 0; - final boolean allowMatchInstant = wantInstantApps - || (wantMatchInstant && canViewInstantApps(callingUid, userId)); - flags &= ~(PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY - | PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY); - if (!allowMatchInstant) { - flags &= ~PackageManager.MATCH_INSTANT; - } - } - return updateFlagsForComponent(flags, userId); + return computer(true).updateFlagsForResolve(flags, userId, callingUid, + wantInstantApps, onlyExposedExplicitly, + isImplicitImageCaptureIntentAndNotSetByDpc); } @Override @@ -5637,69 +8204,30 @@ public class PackageManagerService extends IPackageManager.Stub @Override public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { - return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId); + // SNAPSHOT + return computer(false).getActivityInfo(component, flags, userId); } /** * Important: The provided filterCallingUid is used exclusively to filter out activities * that can be seen based on user state. It's typically the original caller uid prior - * to clearing. Because it can only be provided by trusted code, it's value can be + * to clearing. Because it can only be provided by trusted code, its value can be * trusted and will be used as-is; unlike userId which will be validated by this method. */ private ActivityInfo getActivityInfoInternal(ComponentName component, int flags, int filterCallingUid, int userId) { - if (!mUserManager.exists(userId)) return null; - flags = updateFlagsForComponent(flags, userId); - - if (!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId)) { - enforceCrossUserPermission(Binder.getCallingUid(), userId, - false /* requireFullPermission */, false /* checkShell */, "get activity info"); - } - - return getActivityInfoInternalBody(component, flags, filterCallingUid, userId); + return computer(true).getActivityInfoInternal(component, flags, + filterCallingUid, userId); } private ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags, int filterCallingUid, int userId) { - synchronized (mLock) { - ParsedActivity a = mComponentResolver.getActivity(component); - - if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a); - - AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName()); - if (pkg != null && mSettings.isEnabledAndMatchLPr(pkg, a, flags, userId)) { - PackageSetting ps = mSettings.getPackageLPr(component.getPackageName()); - if (ps == null) return null; - if (shouldFilterApplicationLocked( - ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) { - return null; - } - return PackageInfoUtils.generateActivityInfo(pkg, - a, flags, ps.readUserState(userId), userId, ps); - } - if (mResolveComponentName.equals(component)) { - return PackageParser.generateActivityInfo( - mResolveActivity, flags, new PackageUserState(), userId); - } - } - return null; + return computer(true).getActivityInfoInternalBody(component, flags, + filterCallingUid, userId); } private boolean isRecentsAccessingChildProfiles(int callingUid, int targetUserId) { - if (!mInjector.getLocalService(ActivityTaskManagerInternal.class) - .isCallerRecents(callingUid)) { - return false; - } - final long token = Binder.clearCallingIdentity(); - try { - final int callingUserId = UserHandle.getUserId(callingUid); - if (ActivityManager.getCurrentUser() != callingUserId) { - return false; - } - return mUserManager.isSameProfileGroup(callingUserId, targetUserId); - } finally { - Binder.restoreCallingIdentity(token); - } + return computer(true).isRecentsAccessingChildProfiles(callingUid, targetUserId); } @Override @@ -5962,37 +8490,14 @@ public class PackageManagerService extends IPackageManager.Stub @Override public ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) { - if (!mUserManager.exists(userId)) return null; - final int callingUid = Binder.getCallingUid(); - flags = updateFlagsForComponent(flags, userId); - enforceCrossUserOrProfilePermission(callingUid, userId, false /* requireFullPermission */, - false /* checkShell */, "get service info"); - return getServiceInfoBody(component, flags, userId, callingUid); + // SNAPSHOT + return computer(false).getServiceInfo(component, flags, userId); } private ServiceInfo getServiceInfoBody(ComponentName component, int flags, int userId, int callingUid) { - synchronized (mLock) { - ParsedService s = mComponentResolver.getService(component); - if (DEBUG_PACKAGE_INFO) Log.v( - TAG, "getServiceInfo " + component + ": " + s); - if (s == null) { - return null; - } - - AndroidPackage pkg = mPackages.get(s.getPackageName()); - if (mSettings.isEnabledAndMatchLPr(pkg, s, flags, userId)) { - PackageSetting ps = mSettings.getPackageLPr(component.getPackageName()); - if (ps == null) return null; - if (shouldFilterApplicationLocked( - ps, callingUid, component, TYPE_SERVICE, userId)) { - return null; - } - return PackageInfoUtils.generateServiceInfo(pkg, - s, flags, ps.readUserState(userId), userId, ps); - } - } - return null; + return computer(true).getServiceInfoBody(component, flags, userId, + callingUid); } @Override @@ -6203,7 +8708,8 @@ public class PackageManagerService extends IPackageManager.Stub // NOTE: Can't remove without a major refactor. Keep around for now. @Override public int checkUidPermission(String permName, int uid) { - return mPermissionManager.checkUidPermission(uid, permName); + // SNAPSHOT + return computer(false).checkUidPermission(permName, uid); } @Override @@ -6518,45 +9024,18 @@ public class PackageManagerService extends IPackageManager.Stub */ @Override public String[] getPackagesForUid(int uid) { - return getPackagesForUidInternal(uid, Binder.getCallingUid()); + // SNAPSHOT + return computer(false).getPackagesForUid(uid); } private String[] getPackagesForUidInternal(int uid, int callingUid) { - final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null; - final int userId = UserHandle.getUserId(uid); - final int appId = UserHandle.getAppId(uid); - return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp); + return computer(true).getPackagesForUidInternal(uid, callingUid); } private String[] getPackagesForUidInternalBody(int callingUid, int userId, int appId, boolean isCallerInstantApp) { - // reader - synchronized (mLock) { - final Object obj = mSettings.getSettingLPr(appId); - if (obj instanceof SharedUserSetting) { - if (isCallerInstantApp) { - return null; - } - final SharedUserSetting sus = (SharedUserSetting) obj; - final int N = sus.packages.size(); - String[] res = new String[N]; - int i = 0; - for (int index = 0; index < N; index++) { - final PackageSetting ps = sus.packages.valueAt(index); - if (ps.getInstalled(userId)) { - res[i++] = ps.name; - } - } - return ArrayUtils.trimToSize(res, i); - } else if (obj instanceof PackageSetting) { - final PackageSetting ps = (PackageSetting) obj; - if (ps.getInstalled(userId) - && !shouldFilterApplicationLocked(ps, callingUid, userId)) { - return new String[]{ps.name}; - } - } - } - return null; + return computer(true).getPackagesForUidInternalBody(callingUid, userId, appId, + isCallerInstantApp); } @Override @@ -6844,45 +9323,15 @@ public class PackageManagerService extends IPackageManager.Stub * Returns whether or not instant apps have been disabled remotely. */ private boolean areWebInstantAppsDisabled(int userId) { - return mWebInstantAppsDisabled.get(userId); + return computer(true).areWebInstantAppsDisabled(userId); } private boolean isInstantAppResolutionAllowed( Intent intent, List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck) { - if (mInstantAppResolverConnection == null) { - return false; - } - if (mInstantAppInstallerActivity == null) { - return false; - } - if (intent.getComponent() != null) { - return false; - } - if ((intent.getFlags() & Intent.FLAG_IGNORE_EPHEMERAL) != 0) { - return false; - } - if (!skipPackageCheck && intent.getPackage() != null) { - return false; - } - if (!intent.isWebIntent()) { - // for non web intents, we should not resolve externally if an app already exists to - // handle it or if the caller didn't explicitly request it. - if ((resolvedActivities != null && resolvedActivities.size() != 0) - || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) == 0) { - return false; - } - } else { - if (intent.getData() == null || TextUtils.isEmpty(intent.getData().getHost())) { - return false; - } else if (areWebInstantAppsDisabled(userId)) { - return false; - } - } - // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. - // Or if there's already an ephemeral app installed that handles the action - return isInstantAppResolutionAllowedBody(intent, resolvedActivities, userId, - skipPackageCheck); + return computer(true).isInstantAppResolutionAllowed( + intent, resolvedActivities, userId, + skipPackageCheck); } // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. @@ -6890,39 +9339,9 @@ public class PackageManagerService extends IPackageManager.Stub private boolean isInstantAppResolutionAllowedBody( Intent intent, List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck) { - synchronized (mLock) { - final int count = (resolvedActivities == null ? 0 : resolvedActivities.size()); - for (int n = 0; n < count; n++) { - final ResolveInfo info = resolvedActivities.get(n); - final String packageName = info.activityInfo.packageName; - final PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps != null) { - // only check domain verification status if the app is not a browser - if (!info.handleAllWebDataURI) { - // Try to get the status from User settings first - final long packedStatus = getDomainVerificationStatusLPr(ps, userId); - final int status = (int) (packedStatus >> 32); - if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS - || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) { - if (DEBUG_INSTANT) { - Slog.v(TAG, "DENY instant app;" - + " pkg: " + packageName + ", status: " + status); - } - return false; - } - } - if (ps.getInstantApp(userId)) { - if (DEBUG_INSTANT) { - Slog.v(TAG, "DENY instant app installed;" - + " pkg: " + packageName); - } - return false; - } - } - } - } - // We've exhausted all ways to deny ephemeral application; let the system look for them. - return true; + return computer(true).isInstantAppResolutionAllowedBody( + intent, resolvedActivities, userId, + skipPackageCheck); } private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj, @@ -7059,25 +9478,14 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mLock") private boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId, String resolvedType, int flags) { - return intent.isImplicitImageCaptureIntent() && !isPersistentPreferredActivitySetByDpm( - intent, userId, resolvedType, flags); + return computer(true).isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, + resolvedType, flags); } private boolean isPersistentPreferredActivitySetByDpm(Intent intent, int userId, String resolvedType, int flags) { - PersistentPreferredIntentResolver ppir = mSettings.getPersistentPreferredActivities(userId); - //TODO(b/158003772): Remove double query - List<PersistentPreferredActivity> pprefs = ppir != null - ? ppir.queryIntent(intent, resolvedType, - (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, - userId) - : new ArrayList<>(); - for (PersistentPreferredActivity ppa : pprefs) { - if (ppa.mIsSetByDpm) { - return true; - } - } - return false; + return computer(true).isPersistentPreferredActivitySetByDpm(intent, userId, + resolvedType, flags); } @GuardedBy("mLock") @@ -7401,21 +9809,13 @@ public class PackageManagerService extends IPackageManager.Stub } private UserInfo getProfileParent(int userId) { - final long identity = Binder.clearCallingIdentity(); - try { - return mUserManager.getProfileParent(userId); - } finally { - Binder.restoreCallingIdentity(identity); - } + return computer(true).getProfileParent(userId); } private List<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(Intent intent, String resolvedType, int userId) { - CrossProfileIntentResolver resolver = mSettings.getCrossProfileIntentResolver(userId); - if (resolver != null) { - return resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/, userId); - } - return null; + return computer(true).getMatchingCrossProfileIntentFilters(intent, + resolvedType, userId); } @Override @@ -7436,27 +9836,14 @@ public class PackageManagerService extends IPackageManager.Stub * instant, returns {@code null}. */ private String getInstantAppPackageName(int callingUid) { - synchronized (mLock) { - // If the caller is an isolated app use the owner's uid for the lookup. - if (Process.isIsolated(callingUid)) { - callingUid = mIsolatedOwners.get(callingUid); - } - final int appId = UserHandle.getAppId(callingUid); - final Object obj = mSettings.getSettingLPr(appId); - if (obj instanceof PackageSetting) { - final PackageSetting ps = (PackageSetting) obj; - final boolean isInstantApp = ps.getInstantApp(UserHandle.getUserId(callingUid)); - return isInstantApp ? ps.pkg.getPackageName() : null; - } - } - return null; + // SNAPSHOT + return computer(false).getInstantAppPackageName(callingUid); } private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, int flags, int userId) { - return queryIntentActivitiesInternal( - intent, resolvedType, flags, 0 /*privateResolveFlags*/, Binder.getCallingUid(), - userId, false /*resolveForStart*/, true /*allowDynamicSplits*/); + return computer(true).queryIntentActivitiesInternal(intent, + resolvedType, flags, userId); } // Collect the results of queryIntentActivitiesInternalBody into a single class @@ -7479,306 +9866,27 @@ public class PackageManagerService extends IPackageManager.Stub private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits) { - if (!mUserManager.exists(userId)) return Collections.emptyList(); - final String instantAppPkgName = getInstantAppPackageName(filterCallingUid); - enforceCrossUserPermission(Binder.getCallingUid(), userId, - false /* requireFullPermission */, false /* checkShell */, - "query intent activities"); - final String pkgName = intent.getPackage(); - ComponentName comp = intent.getComponent(); - if (comp == null) { - if (intent.getSelector() != null) { - intent = intent.getSelector(); - comp = intent.getComponent(); - } - } - - flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart, - comp != null || pkgName != null /*onlyExposedExplicitly*/, - isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType, - flags)); - if (comp != null) { - final List<ResolveInfo> list = new ArrayList<>(1); - final ActivityInfo ai = getActivityInfo(comp, flags, userId); - if (ai != null) { - // When specifying an explicit component, we prevent the activity from being - // used when either 1) the calling package is normal and the activity is within - // an ephemeral application or 2) the calling package is ephemeral and the - // activity is not visible to ephemeral applications. - final boolean matchInstantApp = - (flags & PackageManager.MATCH_INSTANT) != 0; - final boolean matchVisibleToInstantAppOnly = - (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0; - final boolean matchExplicitlyVisibleOnly = - (flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0; - final boolean isCallerInstantApp = - instantAppPkgName != null; - final boolean isTargetSameInstantApp = - comp.getPackageName().equals(instantAppPkgName); - final boolean isTargetInstantApp = - (ai.applicationInfo.privateFlags - & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0; - final boolean isTargetVisibleToInstantApp = - (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0; - final boolean isTargetExplicitlyVisibleToInstantApp = - isTargetVisibleToInstantApp - && (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0; - final boolean isTargetHiddenFromInstantApp = - !isTargetVisibleToInstantApp - || (matchExplicitlyVisibleOnly && !isTargetExplicitlyVisibleToInstantApp); - final boolean blockInstantResolution = - !isTargetSameInstantApp - && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp) - || (matchVisibleToInstantAppOnly && isCallerInstantApp - && isTargetHiddenFromInstantApp)); - final boolean blockNormalResolution = - !resolveForStart && !isTargetInstantApp && !isCallerInstantApp - && shouldFilterApplicationLocked( - getPackageSettingInternal(ai.applicationInfo.packageName, - Process.SYSTEM_UID), filterCallingUid, userId); - if (!blockInstantResolution && !blockNormalResolution) { - final ResolveInfo ri = new ResolveInfo(); - ri.activityInfo = ai; - list.add(ri); - } - } - - List<ResolveInfo> result = applyPostResolutionFilter( - list, instantAppPkgName, allowDynamicSplits, filterCallingUid, resolveForStart, - userId, intent); - return result; - } - - QueryIntentActivitiesResult lockedResult = - queryIntentActivitiesInternalBody( - intent, resolvedType, flags, filterCallingUid, userId, resolveForStart, - allowDynamicSplits, pkgName, instantAppPkgName); - if (lockedResult.answer != null) { - return lockedResult.answer; - } - - if (lockedResult.addInstant) { - String callingPkgName = getInstantAppPackageName(filterCallingUid); - boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId); - lockedResult.result = maybeAddInstantAppInstaller(lockedResult.result, intent, - resolvedType, flags, userId, resolveForStart, isRequesterInstantApp); - } - if (lockedResult.sortResult) { - Collections.sort(lockedResult.result, RESOLVE_PRIORITY_SORTER); - } - return applyPostResolutionFilter( - lockedResult.result, instantAppPkgName, allowDynamicSplits, filterCallingUid, - resolveForStart, userId, intent); + return computer(true).queryIntentActivitiesInternal(intent, + resolvedType, flags, privateResolveFlags, + filterCallingUid, userId, resolveForStart, allowDynamicSplits); } private @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody( Intent intent, String resolvedType, int flags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits, String pkgName, String instantAppPkgName) { - // reader - synchronized (mLock) { - boolean sortResult = false; - boolean addInstant = false; - List<ResolveInfo> result = null; - if (pkgName == null) { - List<CrossProfileIntentFilter> matchingFilters = - getMatchingCrossProfileIntentFilters(intent, resolvedType, userId); - // Check for results that need to skip the current profile. - ResolveInfo xpResolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent, - resolvedType, flags, userId); - if (xpResolveInfo != null) { - List<ResolveInfo> xpResult = new ArrayList<>(1); - xpResult.add(xpResolveInfo); - return new QueryIntentActivitiesResult( - applyPostResolutionFilter( - filterIfNotSystemUser(xpResult, userId), instantAppPkgName, - allowDynamicSplits, filterCallingUid, resolveForStart, userId, - intent)); - } - - // Check for results in the current profile. - result = filterIfNotSystemUser(mComponentResolver.queryActivities( - intent, resolvedType, flags, userId), userId); - addInstant = isInstantAppResolutionAllowed(intent, result, userId, - false /*skipPackageCheck*/); - // Check for cross profile results. - boolean hasNonNegativePriorityResult = hasNonNegativePriority(result); - xpResolveInfo = queryCrossProfileIntents( - matchingFilters, intent, resolvedType, flags, userId, - hasNonNegativePriorityResult); - if (xpResolveInfo != null && isUserEnabled(xpResolveInfo.targetUserId)) { - boolean isVisibleToUser = filterIfNotSystemUser( - Collections.singletonList(xpResolveInfo), userId).size() > 0; - if (isVisibleToUser) { - result.add(xpResolveInfo); - sortResult = true; - } - } - if (intent.hasWebURI()) { - CrossProfileDomainInfo xpDomainInfo = null; - final UserInfo parent = getProfileParent(userId); - if (parent != null) { - xpDomainInfo = getCrossProfileDomainPreferredLpr(intent, resolvedType, - flags, userId, parent.id); - } - if (xpDomainInfo != null) { - if (xpResolveInfo != null) { - // If we didn't remove it, the cross-profile ResolveInfo would be twice - // in the result. - result.remove(xpResolveInfo); - } - if (result.size() == 0 && !addInstant) { - // No result in current profile, but found candidate in parent user. - // And we are not going to add emphemeral app, so we can return the - // result straight away. - result.add(xpDomainInfo.resolveInfo); - return new QueryIntentActivitiesResult( - applyPostResolutionFilter(result, instantAppPkgName, - allowDynamicSplits, filterCallingUid, resolveForStart, - userId, intent)); - } - } else if (result.size() <= 1 && !addInstant) { - // No result in parent user and <= 1 result in current profile, and we - // are not going to add emphemeral app, so we can return the result without - // further processing. - return new QueryIntentActivitiesResult( - applyPostResolutionFilter(result, instantAppPkgName, - allowDynamicSplits, filterCallingUid, resolveForStart, userId, - intent)); - } - // We have more than one candidate (combining results from current and parent - // profile), so we need filtering and sorting. - result = filterCandidatesWithDomainPreferredActivitiesLPr( - intent, flags, result, xpDomainInfo, userId); - sortResult = true; - } - } else { - final PackageSetting setting = - getPackageSettingInternal(pkgName, Process.SYSTEM_UID); - result = null; - if (setting != null && setting.pkg != null && (resolveForStart - || !shouldFilterApplicationLocked(setting, filterCallingUid, userId))) { - result = filterIfNotSystemUser(mComponentResolver.queryActivities( - intent, resolvedType, flags, setting.pkg.getActivities(), userId), - userId); - } - if (result == null || result.size() == 0) { - // the caller wants to resolve for a particular package; however, there - // were no installed results, so, try to find an ephemeral result - addInstant = isInstantAppResolutionAllowed( - intent, null /*result*/, userId, true /*skipPackageCheck*/); - if (result == null) { - result = new ArrayList<>(); - } - } - } - return new QueryIntentActivitiesResult(sortResult, addInstant, result); - } + return computer(true).queryIntentActivitiesInternalBody( + intent, resolvedType, flags, filterCallingUid, userId, + resolveForStart, allowDynamicSplits, pkgName, + instantAppPkgName); } private List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result, Intent intent, String resolvedType, int flags, int userId, boolean resolveForStart, boolean isRequesterInstantApp) { - // first, check to see if we've got an instant app already installed - final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0; - ResolveInfo localInstantApp = null; - boolean blockResolution = false; - if (!alreadyResolvedLocally) { - final List<ResolveInfo> instantApps = mComponentResolver.queryActivities( - intent, - resolvedType, - flags - | PackageManager.GET_RESOLVED_FILTER - | PackageManager.MATCH_INSTANT - | PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY, - userId); - for (int i = instantApps.size() - 1; i >= 0; --i) { - final ResolveInfo info = instantApps.get(i); - final String packageName = info.activityInfo.packageName; - final PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps.getInstantApp(userId)) { - final long packedStatus = getDomainVerificationStatusLPr(ps, userId); - final int status = (int)(packedStatus >> 32); - if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { - // there's a local instant application installed, but, the user has - // chosen to never use it; skip resolution and don't acknowledge - // an instant application is even available - if (DEBUG_INSTANT) { - Slog.v(TAG, "Instant app marked to never run; pkg: " + packageName); - } - blockResolution = true; - break; - } else { - // we have a locally installed instant application; skip resolution - // but acknowledge there's an instant application available - if (DEBUG_INSTANT) { - Slog.v(TAG, "Found installed instant app; pkg: " + packageName); - } - localInstantApp = info; - break; - } - } - } - } - // no app installed, let's see if one's available - AuxiliaryResolveInfo auxiliaryResponse = null; - if (!blockResolution) { - if (localInstantApp == null) { - // we don't have an instant app locally, resolve externally - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral"); - String token = UUID.randomUUID().toString(); - InstantAppDigest digest = InstantAppResolver.parseDigest(intent); - final InstantAppRequest requestObject = new InstantAppRequest(null /*responseObj*/, - intent /*origIntent*/, resolvedType, null /*callingPackage*/, - null /*callingFeatureId*/, isRequesterInstantApp, userId, - null /*verificationBundle*/, resolveForStart, - digest.getDigestPrefixSecure(), token); - auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne( - mInstantAppResolverConnection, requestObject); - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } else { - // we have an instant application locally, but, we can't admit that since - // callers shouldn't be able to determine prior browsing. create a placeholder - // auxiliary response so the downstream code behaves as if there's an - // instant application available externally. when it comes time to start - // the instant application, we'll do the right thing. - final ApplicationInfo ai = localInstantApp.activityInfo.applicationInfo; - auxiliaryResponse = new AuxiliaryResolveInfo(null /* failureActivity */, - ai.packageName, ai.longVersionCode, null /* splitName */); - } - } - if (intent.isWebIntent() && auxiliaryResponse == null) { - return result; - } - final PackageSetting ps = mSettings.getPackageLPr(mInstantAppInstallerActivity.packageName); - if (ps == null - || !ps.readUserState(userId).isEnabled(mInstantAppInstallerActivity, 0)) { - return result; - } - final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo); - ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo( - mInstantAppInstallerActivity, 0, ps.readUserState(userId), userId); - ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART - | IntentFilter.MATCH_ADJUSTMENT_NORMAL; - // add a non-generic filter - ephemeralInstaller.filter = new IntentFilter(); - if (intent.getAction() != null) { - ephemeralInstaller.filter.addAction(intent.getAction()); - } - if (intent.getData() != null && intent.getData().getPath() != null) { - ephemeralInstaller.filter.addDataPath( - intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL); - } - ephemeralInstaller.isInstantAppAvailable = true; - // make sure this resolver is the default - ephemeralInstaller.isDefault = true; - ephemeralInstaller.auxiliaryInfo = auxiliaryResponse; - if (DEBUG_INSTANT) { - Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); - } - - result.add(ephemeralInstaller); - return result; + return computer(true).maybeAddInstantAppInstaller(result, intent, + resolvedType, flags, userId, resolveForStart, + isRequesterInstantApp); } private static class CrossProfileDomainInfo { @@ -7790,48 +9898,8 @@ public class PackageManagerService extends IPackageManager.Stub private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent, String resolvedType, int flags, int sourceUserId, int parentUserId) { - if (!mUserManager.hasUserRestriction(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, - sourceUserId)) { - return null; - } - List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent, - resolvedType, flags, parentUserId); - - if (resultTargetUser == null || resultTargetUser.isEmpty()) { - return null; - } - CrossProfileDomainInfo result = null; - int size = resultTargetUser.size(); - for (int i = 0; i < size; i++) { - ResolveInfo riTargetUser = resultTargetUser.get(i); - // Intent filter verification is only for filters that specify a host. So don't return - // those that handle all web uris. - if (riTargetUser.handleAllWebDataURI) { - continue; - } - String packageName = riTargetUser.activityInfo.packageName; - PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps == null) { - continue; - } - long verificationState = getDomainVerificationStatusLPr(ps, parentUserId); - int status = (int)(verificationState >> 32); - if (result == null) { - result = new CrossProfileDomainInfo(); - result.resolveInfo = createForwardingResolveInfoUnchecked(new IntentFilter(), - sourceUserId, parentUserId); - result.bestDomainVerificationStatus = status; - } else { - result.bestDomainVerificationStatus = bestDomainVerificationStatus(status, - result.bestDomainVerificationStatus); - } - } - // Don't consider matches with status NEVER across profiles. - if (result != null && result.bestDomainVerificationStatus - == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { - return null; - } - return result; + return computer(true).getCrossProfileDomainPreferredLpr(intent, + resolvedType, flags, sourceUserId, parentUserId); } /** @@ -7839,23 +9907,11 @@ public class PackageManagerService extends IPackageManager.Stub * INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER, which is the worse. */ private int bestDomainVerificationStatus(int status1, int status2) { - if (status1 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { - return status2; - } - if (status2 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { - return status1; - } - return (int) MathUtils.max(status1, status2); + return computer(true).bestDomainVerificationStatus(status1, status2); } private boolean isUserEnabled(int userId) { - final long callingId = Binder.clearCallingIdentity(); - try { - UserInfo userInfo = mUserManager.getUserInfo(userId); - return userInfo != null && userInfo.isEnabled(); - } finally { - Binder.restoreCallingIdentity(callingId); - } + return computer(true).isUserEnabled(userId); } /** @@ -7864,16 +9920,7 @@ public class PackageManagerService extends IPackageManager.Stub * @return filtered list */ private List<ResolveInfo> filterIfNotSystemUser(List<ResolveInfo> resolveInfos, int userId) { - if (userId == UserHandle.USER_SYSTEM) { - return resolveInfos; - } - for (int i = resolveInfos.size() - 1; i >= 0; i--) { - ResolveInfo info = resolveInfos.get(i); - if ((info.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) { - resolveInfos.remove(i); - } - } - return resolveInfos; + return computer(true).filterIfNotSystemUser(resolveInfos, userId); } /** @@ -7890,87 +9937,9 @@ public class PackageManagerService extends IPackageManager.Stub private List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos, String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid, boolean resolveForStart, int userId, Intent intent) { - final boolean blockInstant = intent.isWebIntent() && areWebInstantAppsDisabled(userId); - for (int i = resolveInfos.size() - 1; i >= 0; i--) { - final ResolveInfo info = resolveInfos.get(i); - // remove locally resolved instant app web results when disabled - if (info.isInstantAppAvailable && blockInstant) { - resolveInfos.remove(i); - continue; - } - // allow activities that are defined in the provided package - if (allowDynamicSplits - && info.activityInfo != null - && info.activityInfo.splitName != null - && !ArrayUtils.contains(info.activityInfo.applicationInfo.splitNames, - info.activityInfo.splitName)) { - if (mInstantAppInstallerActivity == null) { - if (DEBUG_INSTALL) { - Slog.v(TAG, "No installer - not adding it to the ResolveInfo list"); - } - resolveInfos.remove(i); - continue; - } - if (blockInstant && isInstantApp(info.activityInfo.packageName, userId)) { - resolveInfos.remove(i); - continue; - } - // requested activity is defined in a split that hasn't been installed yet. - // add the installer to the resolve list - if (DEBUG_INSTALL) { - Slog.v(TAG, "Adding installer to the ResolveInfo list"); - } - final ResolveInfo installerInfo = new ResolveInfo( - mInstantAppInstallerInfo); - final ComponentName installFailureActivity = findInstallFailureActivity( - info.activityInfo.packageName, filterCallingUid, userId); - installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( - installFailureActivity, - info.activityInfo.packageName, - info.activityInfo.applicationInfo.longVersionCode, - info.activityInfo.splitName); - // add a non-generic filter - installerInfo.filter = new IntentFilter(); - - // This resolve info may appear in the chooser UI, so let us make it - // look as the one it replaces as far as the user is concerned which - // requires loading the correct label and icon for the resolve info. - installerInfo.resolvePackageName = info.getComponentInfo().packageName; - installerInfo.labelRes = info.resolveLabelResId(); - installerInfo.icon = info.resolveIconResId(); - installerInfo.isInstantAppAvailable = true; - resolveInfos.set(i, installerInfo); - continue; - } - if (ephemeralPkgName == null) { - // caller is a full app - SettingBase callingSetting = - mSettings.getSettingLPr(UserHandle.getAppId(filterCallingUid)); - PackageSetting resolvedSetting = - getPackageSettingInternal(info.activityInfo.packageName, 0); - if (resolveForStart - || !mAppsFilter.shouldFilterApplication( - filterCallingUid, callingSetting, resolvedSetting, userId)) { - continue; - } - } else if (ephemeralPkgName.equals(info.activityInfo.packageName)) { - // caller is same app; don't need to apply any other filtering - continue; - } else if (resolveForStart - && (intent.isWebIntent() - || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) - && intent.getPackage() == null - && intent.getComponent() == null) { - // ephemeral apps can launch other ephemeral apps indirectly - continue; - } else if (((info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0) - && !info.activityInfo.applicationInfo.isInstantApp()) { - // allow activities that have been explicitly exposed to ephemeral apps - continue; - } - resolveInfos.remove(i); - } - return resolveInfos; + return computer(true).applyPostResolutionFilter(resolveInfos, + ephemeralPkgName, allowDynamicSplits, filterCallingUid, + resolveForStart, userId, intent); } /** @@ -7982,24 +9951,8 @@ public class PackageManagerService extends IPackageManager.Stub */ private @Nullable ComponentName findInstallFailureActivity( String packageName, int filterCallingUid, int userId) { - final Intent failureActivityIntent = new Intent(Intent.ACTION_INSTALL_FAILURE); - failureActivityIntent.setPackage(packageName); - // IMPORTANT: disallow dynamic splits to avoid an infinite loop - final List<ResolveInfo> result = queryIntentActivitiesInternal( - failureActivityIntent, null /*resolvedType*/, 0 /*flags*/, - 0 /*privateResolveFlags*/, filterCallingUid, userId, false /*resolveForStart*/, - false /*allowDynamicSplits*/); - final int NR = result.size(); - if (NR > 0) { - for (int i = 0; i < NR; i++) { - final ResolveInfo info = result.get(i); - if (info.activityInfo.splitName != null) { - continue; - } - return new ComponentName(packageName, info.activityInfo.name); - } - } - return null; + return computer(true).findInstallFailureActivity( + packageName, filterCallingUid, userId); } /** @@ -8007,180 +9960,23 @@ public class PackageManagerService extends IPackageManager.Stub * @return if the list contains a resolve info with non-negative priority */ private boolean hasNonNegativePriority(List<ResolveInfo> resolveInfos) { - return resolveInfos.size() > 0 && resolveInfos.get(0).priority >= 0; + return computer(true).hasNonNegativePriority(resolveInfos); } private List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent, int matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo, int userId) { - final boolean debug = (intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0; - - if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) { - Slog.v(TAG, "Filtering results with preferred activities. Candidates count: " + - candidates.size()); - } - - final ArrayList<ResolveInfo> result = - filterCandidatesWithDomainPreferredActivitiesLPrBody( - intent, matchFlags, candidates, xpDomainInfo, userId, debug); - - if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) { - Slog.v(TAG, "Filtered results with preferred activities. New candidates count: " - + result.size()); - for (ResolveInfo info : result) { - Slog.v(TAG, " + " + info.activityInfo); - } - } - return result; + return computer(true).filterCandidatesWithDomainPreferredActivitiesLPr(intent, + matchFlags, candidates, xpDomainInfo, + userId); } private ArrayList<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPrBody( Intent intent, int matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) { - synchronized (mLock) { - final ArrayList<ResolveInfo> result = new ArrayList<>(); - final ArrayList<ResolveInfo> alwaysList = new ArrayList<>(); - final ArrayList<ResolveInfo> undefinedList = new ArrayList<>(); - final ArrayList<ResolveInfo> alwaysAskList = new ArrayList<>(); - final ArrayList<ResolveInfo> neverList = new ArrayList<>(); - final ArrayList<ResolveInfo> matchAllList = new ArrayList<>(); - final int count = candidates.size(); - // First, try to use linked apps. Partition the candidates into four lists: - // one for the final results, one for the "do not use ever", one for "undefined status" - // and finally one for "browser app type". - for (int n=0; n<count; n++) { - ResolveInfo info = candidates.get(n); - String packageName = info.activityInfo.packageName; - PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps != null) { - // Add to the special match all list (Browser use case) - if (info.handleAllWebDataURI) { - matchAllList.add(info); - continue; - } - // Try to get the status from User settings first - long packedStatus = getDomainVerificationStatusLPr(ps, userId); - int status = (int)(packedStatus >> 32); - int linkGeneration = (int)(packedStatus & 0xFFFFFFFF); - if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) { - if (DEBUG_DOMAIN_VERIFICATION || debug) { - Slog.i(TAG, " + always: " + info.activityInfo.packageName - + " : linkgen=" + linkGeneration); - } - - if (!intent.hasCategory(CATEGORY_BROWSABLE) - || !intent.hasCategory(CATEGORY_DEFAULT)) { - undefinedList.add(info); - continue; - } - - // Use link-enabled generation as preferredOrder, i.e. - // prefer newly-enabled over earlier-enabled. - info.preferredOrder = linkGeneration; - alwaysList.add(info); - } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { - if (DEBUG_DOMAIN_VERIFICATION || debug) { - Slog.i(TAG, " + never: " + info.activityInfo.packageName); - } - neverList.add(info); - } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) { - if (DEBUG_DOMAIN_VERIFICATION || debug) { - Slog.i(TAG, " + always-ask: " + info.activityInfo.packageName); - } - alwaysAskList.add(info); - } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED || - status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK) { - if (DEBUG_DOMAIN_VERIFICATION || debug) { - Slog.i(TAG, " + ask: " + info.activityInfo.packageName); - } - undefinedList.add(info); - } - } - } - - // We'll want to include browser possibilities in a few cases - boolean includeBrowser = false; - - // First try to add the "always" resolution(s) for the current user, if any - if (alwaysList.size() > 0) { - result.addAll(alwaysList); - } else { - // Add all undefined apps as we want them to appear in the disambiguation dialog. - result.addAll(undefinedList); - // Maybe add one for the other profile. - if (xpDomainInfo != null && ( - xpDomainInfo.bestDomainVerificationStatus - != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)) { - result.add(xpDomainInfo.resolveInfo); - } - includeBrowser = true; - } - - // The presence of any 'always ask' alternatives means we'll also offer browsers. - // If there were 'always' entries their preferred order has been set, so we also - // back that off to make the alternatives equivalent - if (alwaysAskList.size() > 0) { - for (ResolveInfo i : result) { - i.preferredOrder = 0; - } - result.addAll(alwaysAskList); - includeBrowser = true; - } - - if (includeBrowser) { - // Also add browsers (all of them or only the default one) - if (DEBUG_DOMAIN_VERIFICATION) { - Slog.v(TAG, " ...including browsers in candidate set"); - } - if ((matchFlags & MATCH_ALL) != 0) { - result.addAll(matchAllList); - } else { - // Browser/generic handling case. If there's a default browser, go straight - // to that (but only if there is no other higher-priority match). - final String defaultBrowserPackageName = mDefaultAppProvider.getDefaultBrowser( - userId); - int maxMatchPrio = 0; - ResolveInfo defaultBrowserMatch = null; - final int numCandidates = matchAllList.size(); - for (int n = 0; n < numCandidates; n++) { - ResolveInfo info = matchAllList.get(n); - // track the highest overall match priority... - if (info.priority > maxMatchPrio) { - maxMatchPrio = info.priority; - } - // ...and the highest-priority default browser match - if (info.activityInfo.packageName.equals(defaultBrowserPackageName)) { - if (defaultBrowserMatch == null - || (defaultBrowserMatch.priority < info.priority)) { - if (debug) { - Slog.v(TAG, "Considering default browser match " + info); - } - defaultBrowserMatch = info; - } - } - } - if (defaultBrowserMatch != null - && defaultBrowserMatch.priority >= maxMatchPrio - && !TextUtils.isEmpty(defaultBrowserPackageName)) - { - if (debug) { - Slog.v(TAG, "Default browser match " + defaultBrowserMatch); - } - result.add(defaultBrowserMatch); - } else { - result.addAll(matchAllList); - } - } - - // If there is nothing selected, add all candidates and remove the ones that the user - // has explicitly put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state - if (result.size() == 0) { - result.addAll(candidates); - result.removeAll(neverList); - } - } - return result; - } + return computer(true).filterCandidatesWithDomainPreferredActivitiesLPrBody( + intent, matchFlags, candidates, + xpDomainInfo, userId, debug); } // Returns a packed value as a long: @@ -8188,66 +9984,24 @@ public class PackageManagerService extends IPackageManager.Stub // high 'int'-sized word: link status: undefined/ask/never/always. // low 'int'-sized word: relative priority among 'always' results. private long getDomainVerificationStatusLPr(PackageSetting ps, int userId) { - long result = ps.getDomainVerificationStatusForUser(userId); - // if none available, get the status - if (result >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) { - if (ps.getIntentFilterVerificationInfo() != null) { - result = ((long)ps.getIntentFilterVerificationInfo().getStatus()) << 32; - } - } - return result; + return computer(true).getDomainVerificationStatusLPr(ps, userId); } private ResolveInfo querySkipCurrentProfileIntents( List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, int flags, int sourceUserId) { - if (matchingFilters != null) { - int size = matchingFilters.size(); - for (int i = 0; i < size; i ++) { - CrossProfileIntentFilter filter = matchingFilters.get(i); - if ((filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0) { - // Checking if there are activities in the target user that can handle the - // intent. - ResolveInfo resolveInfo = createForwardingResolveInfo(filter, intent, - resolvedType, flags, sourceUserId); - if (resolveInfo != null) { - return resolveInfo; - } - } - } - } - return null; + return computer(true).querySkipCurrentProfileIntents( + matchingFilters, intent, resolvedType, + flags, sourceUserId); } // Return matching ResolveInfo in target user if any. private ResolveInfo queryCrossProfileIntents( List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, int flags, int sourceUserId, boolean matchInCurrentProfile) { - if (matchingFilters != null) { - // Two {@link CrossProfileIntentFilter}s can have the same targetUserId and - // match the same intent. For performance reasons, it is better not to - // run queryIntent twice for the same userId - SparseBooleanArray alreadyTriedUserIds = new SparseBooleanArray(); - int size = matchingFilters.size(); - for (int i = 0; i < size; i++) { - CrossProfileIntentFilter filter = matchingFilters.get(i); - int targetUserId = filter.getTargetUserId(); - boolean skipCurrentProfile = - (filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0; - boolean skipCurrentProfileIfNoMatchFound = - (filter.getFlags() & PackageManager.ONLY_IF_NO_MATCH_FOUND) != 0; - if (!skipCurrentProfile && !alreadyTriedUserIds.get(targetUserId) - && (!skipCurrentProfileIfNoMatchFound || !matchInCurrentProfile)) { - // Checking if there are activities in the target user that can handle the - // intent. - ResolveInfo resolveInfo = createForwardingResolveInfo(filter, intent, - resolvedType, flags, sourceUserId); - if (resolveInfo != null) return resolveInfo; - alreadyTriedUserIds.put(targetUserId, true); - } - } - } - return null; + return computer(true).queryCrossProfileIntents( + matchingFilters, intent, resolvedType, + flags, sourceUserId, matchInCurrentProfile); } /** @@ -8257,54 +10011,14 @@ public class PackageManagerService extends IPackageManager.Stub */ private ResolveInfo createForwardingResolveInfo(CrossProfileIntentFilter filter, Intent intent, String resolvedType, int flags, int sourceUserId) { - int targetUserId = filter.getTargetUserId(); - List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent, - resolvedType, flags, targetUserId); - if (resultTargetUser != null && isUserEnabled(targetUserId)) { - // If all the matches in the target profile are suspended, return null. - for (int i = resultTargetUser.size() - 1; i >= 0; i--) { - if ((resultTargetUser.get(i).activityInfo.applicationInfo.flags - & ApplicationInfo.FLAG_SUSPENDED) == 0) { - return createForwardingResolveInfoUnchecked(filter, sourceUserId, - targetUserId); - } - } - } - return null; + return computer(true).createForwardingResolveInfo(filter, intent, + resolvedType, flags, sourceUserId); } private ResolveInfo createForwardingResolveInfoUnchecked(IntentFilter filter, int sourceUserId, int targetUserId) { - ResolveInfo forwardingResolveInfo = new ResolveInfo(); - final long ident = Binder.clearCallingIdentity(); - boolean targetIsProfile; - try { - targetIsProfile = mUserManager.getUserInfo(targetUserId).isManagedProfile(); - } finally { - Binder.restoreCallingIdentity(ident); - } - String className; - if (targetIsProfile) { - className = FORWARD_INTENT_TO_MANAGED_PROFILE; - } else { - className = FORWARD_INTENT_TO_PARENT; - } - ComponentName forwardingActivityComponentName = new ComponentName( - mAndroidApplication.packageName, className); - ActivityInfo forwardingActivityInfo = getActivityInfo(forwardingActivityComponentName, 0, - sourceUserId); - if (!targetIsProfile) { - forwardingActivityInfo.showUserIcon = targetUserId; - forwardingResolveInfo.noResourceId = true; - } - forwardingResolveInfo.activityInfo = forwardingActivityInfo; - forwardingResolveInfo.priority = 0; - forwardingResolveInfo.preferredOrder = 0; - forwardingResolveInfo.match = 0; - forwardingResolveInfo.isDefault = true; - forwardingResolveInfo.filter = filter; - forwardingResolveInfo.targetUserId = targetUserId; - return forwardingResolveInfo; + return computer(true).createForwardingResolveInfoUnchecked(filter, + sourceUserId, targetUserId); } @Override @@ -8623,144 +10337,23 @@ public class PackageManagerService extends IPackageManager.Stub private @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent, String resolvedType, int flags, int userId, int callingUid, boolean includeInstantApps) { - if (!mUserManager.exists(userId)) return Collections.emptyList(); - enforceCrossUserOrProfilePermission(callingUid, - userId, - false /*requireFullPermission*/, - false /*checkShell*/, - "query intent receivers"); - final String instantAppPkgName = getInstantAppPackageName(callingUid); - flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps, - false /* isImplicitImageCaptureIntentAndNotSetByDpc */); - ComponentName comp = intent.getComponent(); - if (comp == null) { - if (intent.getSelector() != null) { - intent = intent.getSelector(); - comp = intent.getComponent(); - } - } - if (comp != null) { - final List<ResolveInfo> list = new ArrayList<>(1); - final ServiceInfo si = getServiceInfo(comp, flags, userId); - if (si != null) { - // When specifying an explicit component, we prevent the service from being - // used when either 1) the service is in an instant application and the - // caller is not the same instant application or 2) the calling package is - // ephemeral and the activity is not visible to ephemeral applications. - final boolean matchInstantApp = - (flags & PackageManager.MATCH_INSTANT) != 0; - final boolean matchVisibleToInstantAppOnly = - (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0; - final boolean isCallerInstantApp = - instantAppPkgName != null; - final boolean isTargetSameInstantApp = - comp.getPackageName().equals(instantAppPkgName); - final boolean isTargetInstantApp = - (si.applicationInfo.privateFlags - & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0; - final boolean isTargetHiddenFromInstantApp = - (si.flags & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0; - final boolean blockInstantResolution = - !isTargetSameInstantApp - && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp) - || (matchVisibleToInstantAppOnly && isCallerInstantApp - && isTargetHiddenFromInstantApp)); - - final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp - && shouldFilterApplicationLocked( - getPackageSettingInternal(si.applicationInfo.packageName, - Process.SYSTEM_UID), callingUid, userId); - if (!blockInstantResolution && !blockNormalResolution) { - final ResolveInfo ri = new ResolveInfo(); - ri.serviceInfo = si; - list.add(ri); - } - } - return list; - } - - return queryIntentServicesInternalBody(intent, resolvedType, flags, - userId, callingUid, instantAppPkgName); + return computer(true).queryIntentServicesInternal(intent, + resolvedType, flags, userId, callingUid, + includeInstantApps); } private @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent, String resolvedType, int flags, int userId, int callingUid, String instantAppPkgName) { - // reader - synchronized (mLock) { - String pkgName = intent.getPackage(); - if (pkgName == null) { - final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(intent, - resolvedType, flags, userId); - if (resolveInfos == null) { - return Collections.emptyList(); - } - return applyPostServiceResolutionFilter( - resolveInfos, instantAppPkgName, userId, callingUid); - } - final AndroidPackage pkg = mPackages.get(pkgName); - if (pkg != null) { - final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(intent, - resolvedType, flags, pkg.getServices(), - userId); - if (resolveInfos == null) { - return Collections.emptyList(); - } - return applyPostServiceResolutionFilter( - resolveInfos, instantAppPkgName, userId, callingUid); - } - return Collections.emptyList(); - } + return computer(true).queryIntentServicesInternalBody(intent, + resolvedType, flags, userId, callingUid, + instantAppPkgName); } private List<ResolveInfo> applyPostServiceResolutionFilter(List<ResolveInfo> resolveInfos, String instantAppPkgName, @UserIdInt int userId, int filterCallingUid) { - for (int i = resolveInfos.size() - 1; i >= 0; i--) { - final ResolveInfo info = resolveInfos.get(i); - if (instantAppPkgName == null) { - SettingBase callingSetting = - mSettings.getSettingLPr(UserHandle.getAppId(filterCallingUid)); - PackageSetting resolvedSetting = - getPackageSettingInternal(info.serviceInfo.packageName, 0); - if (!mAppsFilter.shouldFilterApplication( - filterCallingUid, callingSetting, resolvedSetting, userId)) { - continue; - } - } - final boolean isEphemeralApp = info.serviceInfo.applicationInfo.isInstantApp(); - // allow services that are defined in the provided package - if (isEphemeralApp && instantAppPkgName.equals(info.serviceInfo.packageName)) { - if (info.serviceInfo.splitName != null - && !ArrayUtils.contains(info.serviceInfo.applicationInfo.splitNames, - info.serviceInfo.splitName)) { - // requested service is defined in a split that hasn't been installed yet. - // add the installer to the resolve list - if (DEBUG_INSTANT) { - Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); - } - final ResolveInfo installerInfo = new ResolveInfo( - mInstantAppInstallerInfo); - installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( - null /* installFailureActivity */, - info.serviceInfo.packageName, - info.serviceInfo.applicationInfo.longVersionCode, - info.serviceInfo.splitName); - // add a non-generic filter - installerInfo.filter = new IntentFilter(); - // load resources from the correct package - installerInfo.resolvePackageName = info.getComponentInfo().packageName; - resolveInfos.set(i, installerInfo); - } - continue; - } - // allow services that have been explicitly exposed to ephemeral apps - if (!isEphemeralApp - && ((info.serviceInfo.flags & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0)) { - continue; - } - resolveInfos.remove(i); - } - return resolveInfos; + return computer(true).applyPostServiceResolutionFilter(resolveInfos, + instantAppPkgName, userId, filterCallingUid); } @Override @@ -8905,88 +10498,14 @@ public class PackageManagerService extends IPackageManager.Stub @Override public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) { - final int callingUid = Binder.getCallingUid(); - if (getInstantAppPackageName(callingUid) != null) { - return ParceledListSlice.emptyList(); - } - if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList(); - flags = updateFlagsForPackage(flags, userId); - - enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */, - false /* checkShell */, "get installed packages"); - - return getInstalledPackagesBody(flags, userId, callingUid); + // SNAPSHOT + return computer(false).getInstalledPackages(flags, userId); } private ParceledListSlice<PackageInfo> getInstalledPackagesBody(int flags, int userId, int callingUid) { - // writer - synchronized (mLock) { - final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0; - final boolean listApex = (flags & MATCH_APEX) != 0; - final boolean listFactory = (flags & MATCH_FACTORY_ONLY) != 0; - - ArrayList<PackageInfo> list; - if (listUninstalled) { - list = new ArrayList<>(mSettings.getPackagesLocked().size()); - for (PackageSetting ps : mSettings.getPackagesLocked().values()) { - if (listFactory) { - if (!ps.isSystem()) { - continue; - } - PackageSetting psDisabled = mSettings.getDisabledSystemPkgLPr(ps); - if (psDisabled != null) { - ps = psDisabled; - } - } - if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) { - continue; - } - if (shouldFilterApplicationLocked(ps, callingUid, userId)) { - continue; - } - final PackageInfo pi = generatePackageInfo(ps, flags, userId); - if (pi != null) { - list.add(pi); - } - } - } else { - list = new ArrayList<>(mPackages.size()); - for (AndroidPackage p : mPackages.values()) { - PackageSetting ps = getPackageSetting(p.getPackageName()); - if (listFactory) { - if (!p.isSystem()) { - continue; - } - PackageSetting psDisabled = mSettings.getDisabledSystemPkgLPr(ps); - if (psDisabled != null) { - ps = psDisabled; - } - } - if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) { - continue; - } - if (shouldFilterApplicationLocked(ps, callingUid, userId)) { - continue; - } - final PackageInfo pi = generatePackageInfo(ps, flags, userId); - if (pi != null) { - list.add(pi); - } - } - } - if (listApex) { - if (listFactory) { - list.addAll(mApexManager.getFactoryPackages()); - } else { - list.addAll(mApexManager.getActivePackages()); - } - if (listUninstalled) { - list.addAll(mApexManager.getInactivePackages()); - } - } - return new ParceledListSlice<>(list); - } + return computer(true).getInstalledPackagesBody(flags, userId, + callingUid); } private void addPackageHoldingPermissions(ArrayList<PackageInfo> list, PackageSetting ps, @@ -9163,39 +10682,20 @@ public class PackageManagerService extends IPackageManager.Stub @Override public boolean isInstantApp(String packageName, int userId) { - final int callingUid = Binder.getCallingUid(); - enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, - false /* checkShell */, "isInstantApp"); - - return isInstantAppInternal(packageName, userId, callingUid); + // SNAPSHOT + return computer(false).isInstantApp(packageName, userId); } private boolean isInstantAppInternal(String packageName, @UserIdInt int userId, int callingUid) { - if (HIDE_EPHEMERAL_APIS) { - return false; - } - return isInstantAppInternalBody(packageName, userId, callingUid); + return computer(true).isInstantAppInternal(packageName, userId, + callingUid); } private boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId, int callingUid) { - synchronized (mLock) { - if (Process.isIsolated(callingUid)) { - callingUid = mIsolatedOwners.get(callingUid); - } - final PackageSetting ps = mSettings.getPackageLPr(packageName); - final boolean returnAllowed = - ps != null - && (isCallerSameApp(packageName, callingUid) - || canViewInstantApps(callingUid, userId) - || mInstantAppRegistry.isInstantAccessGranted( - userId, UserHandle.getAppId(callingUid), ps.appId)); - if (returnAllowed) { - return ps.getInstantApp(userId); - } - } - return false; + return computer(true).isInstantAppInternalBody(packageName, userId, + callingUid); } @Override @@ -9252,9 +10752,7 @@ public class PackageManagerService extends IPackageManager.Stub } private boolean isCallerSameApp(String packageName, int uid) { - AndroidPackage pkg = mPackages.get(packageName); - return pkg != null - && UserHandle.getAppId(uid) == pkg.getUid(); + return computer(true).isCallerSameApp(packageName, uid); } @Override @@ -10012,8 +11510,8 @@ public class PackageManagerService extends IPackageManager.Stub */ void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, boolean requireFullPermission, boolean checkShell, String message) { - enforceCrossUserPermission(callingUid, userId, requireFullPermission, checkShell, false, - message); + computer(true).enforceCrossUserPermission(callingUid, userId, + requireFullPermission, checkShell, message); } /** @@ -10029,23 +11527,9 @@ public class PackageManagerService extends IPackageManager.Stub private void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, boolean requireFullPermission, boolean checkShell, boolean requirePermissionWhenSameUser, String message) { - if (userId < 0) { - throw new IllegalArgumentException("Invalid userId " + userId); - } - if (checkShell) { - PackageManagerServiceUtils.enforceShellRestriction(mInjector.getUserManagerInternal(), - UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); - } - final int callingUserId = UserHandle.getUserId(callingUid); - if (hasCrossUserPermission( - callingUid, callingUserId, userId, requireFullPermission, - requirePermissionWhenSameUser)) { - return; - } - String errorMessage = buildInvalidCrossUserPermissionMessage( - callingUid, userId, message, requireFullPermission); - Slog.w(TAG, errorMessage); - throw new SecurityException(errorMessage); + computer(true).enforceCrossUserPermission(callingUid, userId, + requireFullPermission, checkShell, + requirePermissionWhenSameUser, message); } /** @@ -10064,62 +11548,24 @@ public class PackageManagerService extends IPackageManager.Stub */ private void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId, boolean requireFullPermission, boolean checkShell, String message) { - if (userId < 0) { - throw new IllegalArgumentException("Invalid userId " + userId); - } - if (checkShell) { - PackageManagerServiceUtils.enforceShellRestriction(mInjector.getUserManagerInternal(), - UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); - } - final int callingUserId = UserHandle.getUserId(callingUid); - if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission, - /*requirePermissionWhenSameUser= */ false)) { - return; - } - final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId); - if (isSameProfileGroup && PermissionChecker.checkPermissionForPreflight( - mContext, - android.Manifest.permission.INTERACT_ACROSS_PROFILES, - PermissionChecker.PID_UNKNOWN, - callingUid, - getPackage(callingUid).getPackageName()) - == PermissionChecker.PERMISSION_GRANTED) { - return; - } - String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage( - callingUid, userId, message, requireFullPermission, isSameProfileGroup); - Slog.w(TAG, errorMessage); - throw new SecurityException(errorMessage); + computer(true).enforceCrossUserOrProfilePermission(callingUid, userId, + requireFullPermission, checkShell, message); } private boolean hasCrossUserPermission( int callingUid, int callingUserId, int userId, boolean requireFullPermission, boolean requirePermissionWhenSameUser) { - if (!requirePermissionWhenSameUser && userId == callingUserId) { - return true; - } - if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) { - return true; - } - if (requireFullPermission) { - return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL); - } - return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) - || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS); + return computer(true).hasCrossUserPermission( + callingUid, callingUserId, userId, requireFullPermission, + requirePermissionWhenSameUser); } private boolean hasPermission(String permission) { - return mContext.checkCallingOrSelfPermission(permission) - == PackageManager.PERMISSION_GRANTED; + return computer(true).hasPermission(permission); } private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) { - final long identity = Binder.clearCallingIdentity(); - try { - return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId); - } finally { - Binder.restoreCallingIdentity(identity); - } + return computer(true).isSameProfileGroup(callerUserId, userId); } private static String buildInvalidCrossUserPermissionMessage(int callingUid, @@ -10757,7 +12203,7 @@ public class PackageManagerService extends IPackageManager.Stub @Nullable private SharedLibraryInfo getSharedLibraryInfoLPr(String name, long version) { - return getSharedLibraryInfo(name, version, mSharedLibraries, null); + return computer(true).getSharedLibraryInfoLPr(name, version); } @Nullable @@ -12971,6 +14417,7 @@ public class PackageManagerService extends IPackageManager.Stub mResolveComponentName = new ComponentName( mAndroidApplication.packageName, mResolveActivity.name); } + onChanged(); } } @@ -13107,6 +14554,7 @@ public class PackageManagerService extends IPackageManager.Stub mResolveInfo.preferredOrder = 0; mResolveInfo.match = 0; mResolveComponentName = mCustomResolverComponentName; + onChanged(); Slog.i(TAG, "Replacing default ResolverActivity with custom activity: " + mResolveComponentName); } @@ -13118,6 +14566,7 @@ public class PackageManagerService extends IPackageManager.Stub Slog.d(TAG, "Clear ephemeral installer activity"); } mInstantAppInstallerActivity = null; + onChanged(); return; } @@ -13137,6 +14586,7 @@ public class PackageManagerService extends IPackageManager.Stub mInstantAppInstallerInfo.isDefault = true; mInstantAppInstallerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART | IntentFilter.MATCH_ADJUSTMENT_NORMAL; + onChanged(); } private void killApplication(String pkgName, @AppIdInt int appId, String reason) { @@ -19184,84 +20634,18 @@ public class PackageManagerService extends IPackageManager.Stub } private String resolveExternalPackageNameLPr(AndroidPackage pkg) { - if (pkg.getStaticSharedLibName() != null) { - return pkg.getManifestPackageName(); - } - return pkg.getPackageName(); + return computer(true).resolveExternalPackageNameLPr(pkg); } @GuardedBy("mLock") private String resolveInternalPackageNameLPr(String packageName, long versionCode) { - final int callingUid = Binder.getCallingUid(); - return resolveInternalPackageNameInternalLocked(packageName, versionCode, - callingUid); + return computer(true).resolveInternalPackageNameLPr(packageName, versionCode); } private String resolveInternalPackageNameInternalLocked( String packageName, long versionCode, int callingUid) { - // Handle renamed packages - String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName); - packageName = normalizedPackageName != null ? normalizedPackageName : packageName; - - // Is this a static library? - LongSparseArray<SharedLibraryInfo> versionedLib = - mStaticLibsByDeclaringPackage.get(packageName); - if (versionedLib == null || versionedLib.size() <= 0) { - return packageName; - } - - // Figure out which lib versions the caller can see - LongSparseLongArray versionsCallerCanSee = null; - final int callingAppId = UserHandle.getAppId(callingUid); - if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.SHELL_UID - && callingAppId != Process.ROOT_UID) { - versionsCallerCanSee = new LongSparseLongArray(); - String libName = versionedLib.valueAt(0).getName(); - String[] uidPackages = getPackagesForUidInternal(callingUid, callingUid); - if (uidPackages != null) { - for (String uidPackage : uidPackages) { - PackageSetting ps = mSettings.getPackageLPr(uidPackage); - final int libIdx = ArrayUtils.indexOf(ps.usesStaticLibraries, libName); - if (libIdx >= 0) { - final long libVersion = ps.usesStaticLibrariesVersions[libIdx]; - versionsCallerCanSee.append(libVersion, libVersion); - } - } - } - } - - // Caller can see nothing - done - if (versionsCallerCanSee != null && versionsCallerCanSee.size() <= 0) { - return packageName; - } - - // Find the version the caller can see and the app version code - SharedLibraryInfo highestVersion = null; - final int versionCount = versionedLib.size(); - for (int i = 0; i < versionCount; i++) { - SharedLibraryInfo libraryInfo = versionedLib.valueAt(i); - if (versionsCallerCanSee != null && versionsCallerCanSee.indexOfKey( - libraryInfo.getLongVersion()) < 0) { - continue; - } - final long libVersionCode = libraryInfo.getDeclaringPackage().getLongVersionCode(); - if (versionCode != PackageManager.VERSION_CODE_HIGHEST) { - if (libVersionCode == versionCode) { - return libraryInfo.getPackageName(); - } - } else if (highestVersion == null) { - highestVersion = libraryInfo; - } else if (libVersionCode > highestVersion - .getDeclaringPackage().getLongVersionCode()) { - highestVersion = libraryInfo; - } - } - - if (highestVersion != null) { - return highestVersion.getPackageName(); - } - - return packageName; + return computer(true).resolveInternalPackageNameInternalLocked( + packageName, versionCode, callingUid); } boolean isCallerVerifier(int callingUid) { @@ -21311,38 +22695,11 @@ public class PackageManagerService extends IPackageManager.Stub * then reports the most likely home activity or null if there are more than one. */ private ComponentName getDefaultHomeActivity(int userId) { - List<ResolveInfo> allHomeCandidates = new ArrayList<>(); - ComponentName cn = getHomeActivitiesAsUser(allHomeCandidates, userId); - if (cn != null) { - return cn; - } - // TODO: This should not happen since there should always be a default package set for - // ROLE_HOME in RoleManager. Continue with a warning log for now. - Slog.w(TAG, "Default package for ROLE_HOME is not set in RoleManager"); - - // Find the launcher with the highest priority and return that component if there are no - // other home activity with the same priority. - int lastPriority = Integer.MIN_VALUE; - ComponentName lastComponent = null; - final int size = allHomeCandidates.size(); - for (int i = 0; i < size; i++) { - final ResolveInfo ri = allHomeCandidates.get(i); - if (ri.priority > lastPriority) { - lastComponent = ri.activityInfo.getComponentName(); - lastPriority = ri.priority; - } else if (ri.priority == lastPriority) { - // Two components found with same priority. - lastComponent = null; - } - } - return lastComponent; + return computer(true).getDefaultHomeActivity(userId); } private Intent getHomeIntent() { - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_HOME); - intent.addCategory(Intent.CATEGORY_DEFAULT); - return intent; + return computer(true).getHomeIntent(); } private IntentFilter getHomeFilter() { @@ -21354,31 +22711,8 @@ public class PackageManagerService extends IPackageManager.Stub ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates, int userId) { - Intent intent = getHomeIntent(); - List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null, - PackageManager.GET_META_DATA, userId); - allHomeCandidates.clear(); - if (resolveInfos == null) { - return null; - } - allHomeCandidates.addAll(resolveInfos); - - final String packageName = mDefaultAppProvider.getDefaultHome(userId); - if (packageName == null) { - return null; - } - - int resolveInfosSize = resolveInfos.size(); - for (int i = 0; i < resolveInfosSize; i++) { - ResolveInfo resolveInfo = resolveInfos.get(i); - - if (resolveInfo.activityInfo != null && TextUtils.equals( - resolveInfo.activityInfo.packageName, packageName)) { - return new ComponentName(resolveInfo.activityInfo.packageName, - resolveInfo.activityInfo.name); - } - } - return null; + return computer(true).getHomeActivitiesAsUser(allHomeCandidates, + userId); } /** <b>must not hold {@link #mLock}</b> */ @@ -22430,6 +23764,17 @@ public class PackageManagerService extends IPackageManager.Stub 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() { @@ -22520,6 +23865,7 @@ public class PackageManagerService extends IPackageManager.Stub 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"); @@ -22674,6 +24020,8 @@ public class PackageManagerService extends IPackageManager.Stub 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(); @@ -23072,6 +24420,25 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ -24959,23 +26326,13 @@ public class PackageManagerService extends IPackageManager.Stub } private AndroidPackage getPackage(String packageName) { - synchronized (mLock) { - packageName = resolveInternalPackageNameLPr( - packageName, PackageManager.VERSION_CODE_HIGHEST); - return mPackages.get(packageName); - } + // SNAPSHOT + return computer(false).getPackage(packageName); } private AndroidPackage getPackage(int uid) { - synchronized (mLock) { - final String[] packageNames = getPackagesForUidInternal(uid, Process.SYSTEM_UID); - AndroidPackage pkg = null; - final int numPackages = packageNames == null ? 0 : packageNames.length; - for (int i = 0; pkg == null && i < numPackages; i++) { - pkg = mPackages.get(packageNames[i]); - } - return pkg; - } + // SNAPSHOT + return computer(false).getPackage(uid); } private class PackageManagerInternalImpl extends PackageManagerInternal { @@ -26226,15 +27583,12 @@ public class PackageManagerService extends IPackageManager.Stub @Nullable public PackageSetting getPackageSetting(String packageName) { - return getPackageSettingInternal(packageName, Binder.getCallingUid()); + // SNAPSHOT + return computer(false).getPackageSetting(packageName); } private PackageSetting getPackageSettingInternal(String packageName, int callingUid) { - synchronized (mLock) { - packageName = resolveInternalPackageNameInternalLocked( - packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid); - return mSettings.getPackageLPr(packageName); - } + return computer(true).getPackageSettingInternal(packageName, callingUid); } void forEachPackage(Consumer<AndroidPackage> actionLocked) { @@ -26672,6 +28026,88 @@ public class PackageManagerService extends IPackageManager.Stub 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 000000000000..3b306a850b64 --- /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/PersistentPreferredIntentResolver.java b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java index d0f9787bacb8..c1bfcac50772 100644 --- a/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java +++ b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java @@ -19,8 +19,8 @@ package com.android.server.pm; import android.annotation.NonNull; import android.content.IntentFilter; +import com.android.server.WatchableIntentResolver; import com.android.server.utils.Snappable; -import com.android.server.utils.WatchableIntentResolver; public class PersistentPreferredIntentResolver extends WatchableIntentResolver<PersistentPreferredActivity, PersistentPreferredActivity> @@ -47,7 +47,7 @@ public class PersistentPreferredIntentResolver */ public PersistentPreferredIntentResolver snapshot() { PersistentPreferredIntentResolver result = new PersistentPreferredIntentResolver(); - result.doCopy(this); + result.copyFrom(this); return result; } } diff --git a/services/core/java/com/android/server/pm/PreferredIntentResolver.java b/services/core/java/com/android/server/pm/PreferredIntentResolver.java index b62421e31361..0e3b85ca677a 100644 --- a/services/core/java/com/android/server/pm/PreferredIntentResolver.java +++ b/services/core/java/com/android/server/pm/PreferredIntentResolver.java @@ -19,8 +19,8 @@ package com.android.server.pm; import android.annotation.NonNull; import android.content.IntentFilter; +import com.android.server.WatchableIntentResolver; import com.android.server.utils.Snappable; -import com.android.server.utils.WatchableIntentResolver; import java.io.PrintWriter; import java.util.ArrayList; @@ -76,7 +76,7 @@ public class PreferredIntentResolver */ public PreferredIntentResolver snapshot() { PreferredIntentResolver result = new PreferredIntentResolver(); - result.doCopy(this); + result.copyFrom(this); return result; } } diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java index 7924d5d48876..0e8a278f3b6b 100644 --- a/services/core/java/com/android/server/pm/SettingBase.java +++ b/services/core/java/com/android/server/pm/SettingBase.java @@ -49,6 +49,7 @@ public abstract class SettingBase implements Watchable { * * @param observer The {@link Watcher} to be notified when the {@link Watchable} changes. */ + @Override public void registerObserver(@NonNull Watcher observer) { mWatchable.registerObserver(observer); } @@ -59,20 +60,33 @@ public abstract class SettingBase implements Watchable { * * @param observer The {@link Watcher} that should not be in the notification list. */ + @Override public void unregisterObserver(@NonNull Watcher observer) { mWatchable.unregisterObserver(observer); } /** + * Return true if the {@link Watcher) is a registered observer. + * @param observer A {@link Watcher} that might be registered + * @return true if the observer is registered with this {@link Watchable}. + */ + @Override + public boolean isRegisteredObserver(@NonNull Watcher observer) { + return mWatchable.isRegisteredObserver(observer); + } + + /** * Invokes {@link Watcher#onChange} on each registered observer. The method can be called * with the {@link Watchable} that generated the event. In a tree of {@link Watchable}s, this * is generally the first (deepest) {@link Watchable} to detect a change. * * @param what The {@link Watchable} that generated the event. */ + @Override public void dispatchChange(@Nullable Watchable what) { mWatchable.dispatchChange(what); } + /** * Notify listeners that this object has changed. */ diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 7c4dadea89eb..50c1065a6000 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -115,12 +115,15 @@ import com.android.server.pm.permission.LegacyPermissionSettings; import com.android.server.pm.permission.LegacyPermissionState; import com.android.server.pm.permission.LegacyPermissionState.PermissionState; import com.android.server.utils.Snappable; -import com.android.server.utils.Snapshots; import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.utils.Watchable; import com.android.server.utils.WatchableImpl; +import com.android.server.utils.Watched; +import com.android.server.utils.WatchedArrayList; import com.android.server.utils.WatchedArrayMap; +import com.android.server.utils.WatchedArraySet; import com.android.server.utils.WatchedSparseArray; +import com.android.server.utils.WatchedSparseIntArray; import com.android.server.utils.Watcher; import libcore.io.IoUtils; @@ -189,6 +192,16 @@ public final class Settings implements Watchable, Snappable { } /** + * Return true if the {@link Watcher) is a registered observer. + * @param observer A {@link Watcher} that might be registered + * @return true if the observer is registered with this {@link Watchable}. + */ + @Override + public boolean isRegisteredObserver(@NonNull Watcher observer) { + return mWatchable.isRegisteredObserver(observer); + } + + /** * Invokes {@link Watcher#onChange} on each registered observer. The method can be called * with the {@link Watchable} that generated the event. In a tree of {@link Watchable}s, this * is generally the first (deepest) {@link Watchable} to detect a change. @@ -350,6 +363,7 @@ public final class Settings implements Watchable, Snappable { private final File mKernelMappingFilename; /** Map from package name to settings */ + @Watched @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) final WatchedArrayMap<String, PackageSetting> mPackages = new WatchedArrayMap<>(); @@ -357,21 +371,29 @@ public final class Settings implements Watchable, Snappable { * List of packages that were involved in installing other packages, i.e. are listed * in at least one app's InstallSource. */ - private final ArraySet<String> mInstallerPackages = new ArraySet<>(); + @Watched + private final WatchedArraySet<String> mInstallerPackages = new WatchedArraySet<>(); /** Map from package name to appId and excluded userids */ - private final ArrayMap<String, KernelPackageState> mKernelMapping = new ArrayMap<>(); + @Watched + private final WatchedArrayMap<String, KernelPackageState> mKernelMapping = + new WatchedArrayMap<>(); // List of replaced system applications + @Watched @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - final ArrayMap<String, PackageSetting> mDisabledSysPackages = new ArrayMap<>(); + final WatchedArrayMap<String, PackageSetting> mDisabledSysPackages = new WatchedArrayMap<>(); /** List of packages that are blocked for uninstall for specific users */ - private final SparseArray<ArraySet<String>> mBlockUninstallPackages = new SparseArray<>(); + @Watched + private final WatchedSparseArray<ArraySet<String>> mBlockUninstallPackages = + new WatchedSparseArray<>(); // Set of restored intent-filter verification states - private final ArrayMap<String, IntentFilterVerificationInfo> mRestoredIntentFilterVerifications = - new ArrayMap<String, IntentFilterVerificationInfo>(); + @Watched + private final WatchedArrayMap<String, IntentFilterVerificationInfo> + mRestoredIntentFilterVerifications = + new WatchedArrayMap<String, IntentFilterVerificationInfo>(); private static final class KernelPackageState { int appId; @@ -381,7 +403,8 @@ public final class Settings implements Watchable, Snappable { private static int mFirstAvailableUid = 0; /** Map from volume UUID to {@link VersionInfo} */ - private ArrayMap<String, VersionInfo> mVersion = new ArrayMap<>(); + @Watched + private WatchedArrayMap<String, VersionInfo> mVersion = new WatchedArrayMap<>(); /** * Version details for a storage volume that may hold apps. @@ -423,21 +446,27 @@ public final class Settings implements Watchable, Snappable { // The user's preferred activities associated with particular intent // filters. + @Watched private final WatchedSparseArray<PreferredIntentResolver> mPreferredActivities = new WatchedSparseArray<>(); // The persistent preferred activities of the user's profile/device owner // associated with particular intent filters. + @Watched private final WatchedSparseArray<PersistentPreferredIntentResolver> mPersistentPreferredActivities = new WatchedSparseArray<>(); // For every user, it is used to find to which other users the intent can be forwarded. + @Watched private final WatchedSparseArray<CrossProfileIntentResolver> mCrossProfileIntentResolvers = new WatchedSparseArray<>(); - final ArrayMap<String, SharedUserSetting> mSharedUsers = new ArrayMap<>(); - private final ArrayList<SettingBase> mAppIds; - private final SparseArray<SettingBase> mOtherAppIds; + @Watched + final WatchedArrayMap<String, SharedUserSetting> mSharedUsers = new WatchedArrayMap<>(); + @Watched + private final WatchedArrayList<SettingBase> mAppIds; + @Watched + private final WatchedSparseArray<SettingBase> mOtherAppIds; // For reading/writing settings file. private final ArrayList<Signature> mPastSignatures = @@ -449,13 +478,17 @@ public final class Settings implements Watchable, Snappable { // Keys are the new names of the packages, values are the original // names. The packages appear everywhere else under their original // names. - private final ArrayMap<String, String> mRenamedPackages = new ArrayMap<String, String>(); + @Watched + private final WatchedArrayMap<String, String> mRenamedPackages = + new WatchedArrayMap<String, String>(); // For every user, it is used to find the package name of the default Browser App. - final SparseArray<String> mDefaultBrowserApp = new SparseArray<String>(); + @Watched + final WatchedSparseArray<String> mDefaultBrowserApp = new WatchedSparseArray<String>(); // App-link priority tracking, per-user - final SparseIntArray mNextAppLinkGeneration = new SparseIntArray(); + @Watched + final WatchedSparseIntArray mNextAppLinkGeneration = new WatchedSparseIntArray(); final StringBuilder mReadMessages = new StringBuilder(); @@ -471,7 +504,7 @@ public final class Settings implements Watchable, Snappable { private final File mSystemDir; public final KeySetManagerService mKeySetManagerService = - new KeySetManagerService(mPackages.untrackedMap()); + new KeySetManagerService(mPackages.untrackedStorage()); /** Settings and other information about permissions */ final LegacyPermissionSettings mPermissions; @@ -492,8 +525,8 @@ public final class Settings implements Watchable, Snappable { public Settings(Map<String, PackageSetting> pkgSettings) { mLock = new Object(); mPackages.putAll(pkgSettings); - mAppIds = new ArrayList<>(); - mOtherAppIds = new SparseArray<>(); + mAppIds = new WatchedArrayList<>(); + mOtherAppIds = new WatchedSparseArray<>(); mSystemDir = null; mPermissions = null; mRuntimePermissionsPersistence = null; @@ -504,17 +537,32 @@ public final class Settings implements Watchable, Snappable { mStoppedPackagesFilename = null; mBackupStoppedPackagesFilename = null; mKernelMappingFilename = null; + mPackages.registerObserver(mObserver); + mInstallerPackages.registerObserver(mObserver); + mKernelMapping.registerObserver(mObserver); + mDisabledSysPackages.registerObserver(mObserver); + mBlockUninstallPackages.registerObserver(mObserver); + mRestoredIntentFilterVerifications.registerObserver(mObserver); + mVersion.registerObserver(mObserver); mPreferredActivities.registerObserver(mObserver); mPersistentPreferredActivities.registerObserver(mObserver); mCrossProfileIntentResolvers.registerObserver(mObserver); + mSharedUsers.registerObserver(mObserver); + mAppIds.registerObserver(mObserver); + mOtherAppIds.registerObserver(mObserver); + mRenamedPackages.registerObserver(mObserver); + mDefaultBrowserApp.registerObserver(mObserver); + mNextAppLinkGeneration.registerObserver(mObserver); + + Watchable.verifyWatchedAttributes(this, mObserver); } Settings(File dataDir, RuntimePermissionsPersistence runtimePermissionsPersistence, LegacyPermissionDataProvider permissionDataProvider, Object lock) { mLock = lock; - mAppIds = new ArrayList<>(); - mOtherAppIds = new SparseArray<>(); + mAppIds = new WatchedArrayList<>(); + mOtherAppIds = new WatchedSparseArray<>(); mPermissions = new LegacyPermissionSettings(lock); mRuntimePermissionsPersistence = new RuntimePermissionPersistence( runtimePermissionsPersistence); @@ -537,10 +585,25 @@ public final class Settings implements Watchable, Snappable { // Deprecated: Needed for migration mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml"); mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); + mPackages.registerObserver(mObserver); + mInstallerPackages.registerObserver(mObserver); + mKernelMapping.registerObserver(mObserver); + mDisabledSysPackages.registerObserver(mObserver); + mBlockUninstallPackages.registerObserver(mObserver); + mRestoredIntentFilterVerifications.registerObserver(mObserver); + mVersion.registerObserver(mObserver); mPreferredActivities.registerObserver(mObserver); mPersistentPreferredActivities.registerObserver(mObserver); mCrossProfileIntentResolvers.registerObserver(mObserver); + mSharedUsers.registerObserver(mObserver); + mAppIds.registerObserver(mObserver); + mOtherAppIds.registerObserver(mObserver); + mRenamedPackages.registerObserver(mObserver); + mDefaultBrowserApp.registerObserver(mObserver); + mNextAppLinkGeneration.registerObserver(mObserver); + + Watchable.verifyWatchedAttributes(this, mObserver); } /** @@ -568,7 +631,7 @@ public final class Settings implements Watchable, Snappable { mInstallerPackages.addAll(r.mInstallerPackages); mKernelMapping.putAll(r.mKernelMapping); mDisabledSysPackages.putAll(r.mDisabledSysPackages); - Snapshots.copy(mBlockUninstallPackages, r.mBlockUninstallPackages); + mBlockUninstallPackages.snapshot(r.mBlockUninstallPackages); mRestoredIntentFilterVerifications.putAll(r.mRestoredIntentFilterVerifications); mVersion.putAll(r.mVersion); mVerifierDeviceIdentity = r.mVerifierDeviceIdentity; @@ -579,13 +642,13 @@ public final class Settings implements Watchable, Snappable { WatchedSparseArray.snapshot( mCrossProfileIntentResolvers, r.mCrossProfileIntentResolvers); mSharedUsers.putAll(r.mSharedUsers); - mAppIds = new ArrayList<>(r.mAppIds); - mOtherAppIds = r.mOtherAppIds.clone(); + mAppIds = r.mAppIds.snapshot(); + mOtherAppIds = r.mOtherAppIds.snapshot(); mPastSignatures.addAll(r.mPastSignatures); mKeySetRefs.putAll(r.mKeySetRefs); - mRenamedPackages.putAll(r.mRenamedPackages); - Snapshots.copy(mDefaultBrowserApp, r.mDefaultBrowserApp); - Snapshots.snapshot(mNextAppLinkGeneration, r.mNextAppLinkGeneration); + mRenamedPackages.snapshot(r.mRenamedPackages); + mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp); + mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration); // mReadMessages mPendingPackages.addAll(r.mPendingPackages); mSystemDir = null; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 9347ce1c6c0f..e20ed05dc4b4 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -3646,6 +3646,7 @@ public class UserManagerService extends IUserManager.Stub { //...then external ones Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED); + addedIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id); // Also, add the UserHandle for mainline modules which can't use the @hide // EXTRA_USER_HANDLE. @@ -4048,6 +4049,7 @@ public class UserManagerService extends IUserManager.Stub { final long ident = Binder.clearCallingIdentity(); try { Intent removedIntent = new Intent(Intent.ACTION_USER_REMOVED); + removedIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); // Also, add the UserHandle for mainline modules which can't use the @hide // EXTRA_USER_HANDLE. 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 004c0154963b..8f422890c973 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { 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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 6e4806f84bf4..aa2e1ff68ced 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3686,6 +3686,32 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; } + case KeyEvent.KEYCODE_TV_POWER: { + result &= ~ACTION_PASS_TO_USER; + isWakeKey = false; // wake-up will be handled separately + HdmiControlManager hdmiControlManager = getHdmiControlManager(); + if (hdmiControlManager != null && hdmiControlManager.shouldHandleTvPowerKey()) { + if (down) { + hdmiControlManager.toggleAndFollowTvPower(); + } + } else if (mHasFeatureLeanback) { + KeyEvent fallbackEvent = KeyEvent.obtain( + event.getDownTime(), event.getEventTime(), + event.getAction(), KeyEvent.KEYCODE_POWER, + event.getRepeatCount(), event.getMetaState(), + event.getDeviceId(), event.getScanCode(), + event.getFlags(), event.getSource(), event.getDisplayId(), null); + if (down) { + interceptPowerKeyDown(fallbackEvent, interactive); + } else { + interceptPowerKeyUp(fallbackEvent, interactive, canceled); + } + } + // Ignore this key for any device that is not connected to a TV via HDMI and + // not an Android TV device. + break; + } + case KeyEvent.KEYCODE_POWER: { EventLogTags.writeInterceptPower( KeyEvent.actionToString(event.getAction()), diff --git a/services/core/java/com/android/server/powerstats/BatteryTrigger.java b/services/core/java/com/android/server/powerstats/BatteryTrigger.java index 6500523198ba..b35cb52d5025 100644 --- a/services/core/java/com/android/server/powerstats/BatteryTrigger.java +++ b/services/core/java/com/android/server/powerstats/BatteryTrigger.java @@ -43,7 +43,7 @@ public final class BatteryTrigger extends PowerStatsLogTrigger { if (newBatteryLevel < mBatteryLevel) { if (DEBUG) Slog.d(TAG, "Battery level dropped. Log rail data"); - logPowerStatsData(); + logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP); } mBatteryLevel = newBatteryLevel; diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java index c9595c2eec2b..6d9cb7522bef 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java @@ -218,7 +218,7 @@ public class PowerStatsDataStorage { * array and written to on-device storage. */ public void write(byte[] data) { - if (data.length > 0) { + if (data != null && data.length > 0) { mLock.lock(); long currentTimeMillis = System.currentTimeMillis(); diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogTrigger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogTrigger.java index 1754185ea71b..e3672a8d09ca 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsLogTrigger.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsLogTrigger.java @@ -31,10 +31,8 @@ public abstract class PowerStatsLogTrigger { protected Context mContext; private PowerStatsLogger mPowerStatsLogger; - protected void logPowerStatsData() { - Message.obtain( - mPowerStatsLogger, - PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE).sendToTarget(); + protected void logPowerStatsData(int msgType) { + Message.obtain(mPowerStatsLogger, msgType).sendToTarget(); } public PowerStatsLogTrigger(Context context, PowerStatsLogger powerStatsLogger) { diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java index 409cd826b6bc..9ee34298a145 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java @@ -20,6 +20,8 @@ import android.content.Context; import android.hardware.power.stats.ChannelInfo; import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyMeasurement; +import android.hardware.power.stats.PowerEntityInfo; +import android.hardware.power.stats.StateResidencyResult; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -32,6 +34,8 @@ import com.android.server.powerstats.ProtoStreamUtils.ChannelInfoUtils; import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerIdUtils; import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils; import com.android.server.powerstats.ProtoStreamUtils.EnergyMeasurementUtils; +import com.android.server.powerstats.ProtoStreamUtils.PowerEntityInfoUtils; +import com.android.server.powerstats.ProtoStreamUtils.StateResidencyResultUtils; import java.io.ByteArrayInputStream; import java.io.File; @@ -42,23 +46,25 @@ import java.io.IOException; * PowerStatsLogger is responsible for logging model and meter energy data to on-device storage. * Messages are sent to its message handler to request that energy data be logged, at which time it * queries the PowerStats HAL and logs the data to on-device storage. The on-device storage is - * dumped to file by calling writeModelDataToFile or writeMeterDataToFile with a file descriptor - * that points to the output file. + * dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or writeResidencyDataToFile + * with a file descriptor that points to the output file. */ public final class PowerStatsLogger extends Handler { private static final String TAG = PowerStatsLogger.class.getSimpleName(); private static final boolean DEBUG = false; - protected static final int MSG_LOG_TO_DATA_STORAGE = 0; + protected static final int MSG_LOG_TO_DATA_STORAGE_TIMER = 0; + protected static final int MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP = 1; private final PowerStatsDataStorage mPowerStatsMeterStorage; private final PowerStatsDataStorage mPowerStatsModelStorage; + private final PowerStatsDataStorage mPowerStatsResidencyStorage; private final IPowerStatsHALWrapper mPowerStatsHALWrapper; @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_LOG_TO_DATA_STORAGE: - if (DEBUG) Slog.d(TAG, "Logging to data storage"); + case MSG_LOG_TO_DATA_STORAGE_TIMER: + if (DEBUG) Slog.d(TAG, "Logging to data storage on timer"); // Log power meter data. EnergyMeasurement[] energyMeasurements = @@ -74,6 +80,17 @@ public final class PowerStatsLogger extends Handler { EnergyConsumerResultUtils.getProtoBytes(energyConsumerResults)); if (DEBUG) EnergyConsumerResultUtils.print(energyConsumerResults); break; + + case MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP: + if (DEBUG) Slog.d(TAG, "Logging to data storage on battery drop"); + + // Log state residency data. + StateResidencyResult[] stateResidencyResults = + mPowerStatsHALWrapper.getStateResidency(new int[0]); + mPowerStatsResidencyStorage.write( + StateResidencyResultUtils.getProtoBytes(stateResidencyResults)); + if (DEBUG) StateResidencyResultUtils.print(stateResidencyResults); + break; } } @@ -159,13 +176,57 @@ public final class PowerStatsLogger extends Handler { pos.flush(); } + /** + * Writes residency data stored in PowerStatsDataStorage to a file descriptor. + * + * @param fd FileDescriptor where residency data stored in PowerStatsDataStorage is written. + * Data is written in protobuf format as defined by powerstatsservice.proto. + */ + public void writeResidencyDataToFile(FileDescriptor fd) { + if (DEBUG) Slog.d(TAG, "Writing residency data to file"); + + final ProtoOutputStream pos = new ProtoOutputStream(fd); + + try { + PowerEntityInfo[] powerEntityInfo = mPowerStatsHALWrapper.getPowerEntityInfo(); + PowerEntityInfoUtils.packProtoMessage(powerEntityInfo, pos); + if (DEBUG) PowerEntityInfoUtils.print(powerEntityInfo); + + mPowerStatsResidencyStorage.read(new PowerStatsDataStorage.DataElementReadCallback() { + @Override + public void onReadDataElement(byte[] data) { + try { + final ProtoInputStream pis = + new ProtoInputStream(new ByteArrayInputStream(data)); + // TODO(b/166535853): ProtoOutputStream doesn't provide a method to write + // a byte array that already contains a serialized proto, so I have to + // deserialize, then re-serialize. This is computationally inefficient. + StateResidencyResult[] stateResidencyResult = + StateResidencyResultUtils.unpackProtoMessage(data); + StateResidencyResultUtils.packProtoMessage(stateResidencyResult, pos); + if (DEBUG) StateResidencyResultUtils.print(stateResidencyResult); + } catch (IOException e) { + Slog.e(TAG, "Failed to write residency data to incident report."); + } + } + }); + } catch (IOException e) { + Slog.e(TAG, "Failed to write residency data to incident report."); + } + + pos.flush(); + } + public PowerStatsLogger(Context context, File dataStoragePath, String meterFilename, - String modelFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { + String modelFilename, String residencyFilename, + IPowerStatsHALWrapper powerStatsHALWrapper) { super(Looper.getMainLooper()); mPowerStatsHALWrapper = powerStatsHALWrapper; mPowerStatsMeterStorage = new PowerStatsDataStorage(context, dataStoragePath, meterFilename); mPowerStatsModelStorage = new PowerStatsDataStorage(context, dataStoragePath, modelFilename); + mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, dataStoragePath, + residencyFilename); } } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index ce50e5833c45..777857209de0 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -49,6 +49,8 @@ public class PowerStatsService extends SystemService { private static final int DATA_STORAGE_VERSION = 0; private static final String METER_FILENAME = "log.powerstats.meter." + DATA_STORAGE_VERSION; private static final String MODEL_FILENAME = "log.powerstats.model." + DATA_STORAGE_VERSION; + private static final String RESIDENCY_FILENAME = + "log.powerstats.residency." + DATA_STORAGE_VERSION; private final Injector mInjector; @@ -76,15 +78,19 @@ public class PowerStatsService extends SystemService { return MODEL_FILENAME; } + String createResidencyFilename() { + return RESIDENCY_FILENAME; + } + IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() { return PowerStatsHALWrapper.getPowerStatsHalImpl(); } PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath, - String meterFilename, String modelFilename, + String meterFilename, String modelFilename, String residencyFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { return new PowerStatsLogger(context, dataStoragePath, meterFilename, - modelFilename, powerStatsHALWrapper); + modelFilename, residencyFilename, powerStatsHALWrapper); } BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) { @@ -109,6 +115,8 @@ public class PowerStatsService extends SystemService { mPowerStatsLogger.writeModelDataToFile(fd); } else if ("meter".equals(args[1])) { mPowerStatsLogger.writeMeterDataToFile(fd); + } else if ("residency".equals(args[1])) { + mPowerStatsLogger.writeResidencyDataToFile(fd); } } else if (args.length == 0) { pw.println("PowerStatsService dumpsys: available PowerEntityInfos"); @@ -148,7 +156,8 @@ public class PowerStatsService extends SystemService { // Only start logger and triggers if initialization is successful. mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mInjector.createDataStoragePath(), mInjector.createMeterFilename(), - mInjector.createModelFilename(), mPowerStatsHALWrapper); + mInjector.createModelFilename(), mInjector.createResidencyFilename(), + mPowerStatsHALWrapper); mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger); mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger); } else { diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java index 5e23b86e0adb..ab9b3e0c41cd 100644 --- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java +++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java @@ -20,6 +20,8 @@ import android.hardware.power.stats.ChannelInfo; import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyMeasurement; import android.hardware.power.stats.PowerEntityInfo; +import android.hardware.power.stats.StateInfo; +import android.hardware.power.stats.StateResidency; import android.hardware.power.stats.StateResidencyResult; import android.util.Slog; import android.util.proto.ProtoInputStream; @@ -45,6 +47,29 @@ public class ProtoStreamUtils { private static final String TAG = ProtoStreamUtils.class.getSimpleName(); static class PowerEntityInfoUtils { + public static void packProtoMessage(PowerEntityInfo[] powerEntityInfo, + ProtoOutputStream pos) { + if (powerEntityInfo == null) return; + + for (int i = 0; i < powerEntityInfo.length; i++) { + long peiToken = pos.start(PowerStatsServiceResidencyProto.POWER_ENTITY_INFO); + pos.write(PowerEntityInfoProto.POWER_ENTITY_ID, powerEntityInfo[i].powerEntityId); + pos.write(PowerEntityInfoProto.POWER_ENTITY_NAME, + powerEntityInfo[i].powerEntityName); + if (powerEntityInfo[i].states != null) { + final int statesLength = powerEntityInfo[i].states.length; + for (int j = 0; j < statesLength; j++) { + final StateInfo state = powerEntityInfo[i].states[j]; + long siToken = pos.start(PowerEntityInfoProto.STATES); + pos.write(StateInfoProto.STATE_ID, state.stateId); + pos.write(StateInfoProto.STATE_NAME, state.stateName); + pos.end(siToken); + } + } + pos.end(peiToken); + } + } + public static void print(PowerEntityInfo[] powerEntityInfo) { if (powerEntityInfo == null) return; @@ -77,6 +102,144 @@ public class ProtoStreamUtils { } static class StateResidencyResultUtils { + public static byte[] getProtoBytes(StateResidencyResult[] stateResidencyResult) { + ProtoOutputStream pos = new ProtoOutputStream(); + packProtoMessage(stateResidencyResult, pos); + return pos.getBytes(); + } + + public static void packProtoMessage(StateResidencyResult[] stateResidencyResult, + ProtoOutputStream pos) { + if (stateResidencyResult == null) return; + + for (int i = 0; i < stateResidencyResult.length; i++) { + final int stateLength = stateResidencyResult[i].stateResidencyData.length; + long srrToken = pos.start(PowerStatsServiceResidencyProto.STATE_RESIDENCY_RESULT); + pos.write(StateResidencyResultProto.POWER_ENTITY_ID, + stateResidencyResult[i].powerEntityId); + for (int j = 0; j < stateLength; j++) { + final StateResidency stateResidencyData = + stateResidencyResult[i].stateResidencyData[j]; + long srdToken = pos.start(StateResidencyResultProto.STATE_RESIDENCY_DATA); + pos.write(StateResidencyProto.STATE_ID, stateResidencyData.stateId); + pos.write(StateResidencyProto.TOTAL_TIME_IN_STATE_MS, + stateResidencyData.totalTimeInStateMs); + pos.write(StateResidencyProto.TOTAL_STATE_ENTRY_COUNT, + stateResidencyData.totalStateEntryCount); + pos.write(StateResidencyProto.LAST_ENTRY_TIMESTAMP_MS, + stateResidencyData.lastEntryTimestampMs); + pos.end(srdToken); + } + pos.end(srrToken); + } + } + + public static StateResidencyResult[] unpackProtoMessage(byte[] data) throws IOException { + final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data)); + List<StateResidencyResult> stateResidencyResultList = + new ArrayList<StateResidencyResult>(); + while (true) { + try { + int nextField = pis.nextField(); + StateResidencyResult stateResidencyResult = new StateResidencyResult(); + + if (nextField == (int) PowerStatsServiceResidencyProto.STATE_RESIDENCY_RESULT) { + long token = + pis.start(PowerStatsServiceResidencyProto.STATE_RESIDENCY_RESULT); + stateResidencyResultList.add(unpackStateResidencyResultProto(pis)); + pis.end(token); + } else if (nextField == ProtoInputStream.NO_MORE_FIELDS) { + return stateResidencyResultList.toArray( + new StateResidencyResult[stateResidencyResultList.size()]); + } else { + Slog.e(TAG, "Unhandled field in PowerStatsServiceResidencyProto: " + + ProtoUtils.currentFieldToString(pis)); + } + } catch (WireTypeMismatchException wtme) { + Slog.e(TAG, "Wire Type mismatch in PowerStatsServiceResidencyProto: " + + ProtoUtils.currentFieldToString(pis)); + } + } + } + + private static StateResidencyResult unpackStateResidencyResultProto(ProtoInputStream pis) + throws IOException { + StateResidencyResult stateResidencyResult = new StateResidencyResult(); + List<StateResidency> stateResidencyList = new ArrayList<StateResidency>(); + + while (true) { + try { + switch (pis.nextField()) { + case (int) StateResidencyResultProto.POWER_ENTITY_ID: + stateResidencyResult.powerEntityId = + pis.readInt(StateResidencyResultProto.POWER_ENTITY_ID); + break; + + case (int) StateResidencyResultProto.STATE_RESIDENCY_DATA: + long token = pis.start(StateResidencyResultProto.STATE_RESIDENCY_DATA); + stateResidencyList.add(unpackStateResidencyProto(pis)); + pis.end(token); + break; + + case ProtoInputStream.NO_MORE_FIELDS: + stateResidencyResult.stateResidencyData = stateResidencyList.toArray( + new StateResidency[stateResidencyList.size()]); + return stateResidencyResult; + + default: + Slog.e(TAG, "Unhandled field in StateResidencyResultProto: " + + ProtoUtils.currentFieldToString(pis)); + break; + } + } catch (WireTypeMismatchException wtme) { + Slog.e(TAG, "Wire Type mismatch in StateResidencyResultProto: " + + ProtoUtils.currentFieldToString(pis)); + } + } + } + + private static StateResidency unpackStateResidencyProto(ProtoInputStream pis) + throws IOException { + StateResidency stateResidency = new StateResidency(); + + while (true) { + try { + switch (pis.nextField()) { + case (int) StateResidencyProto.STATE_ID: + stateResidency.stateId = pis.readInt(StateResidencyProto.STATE_ID); + break; + + case (int) StateResidencyProto.TOTAL_TIME_IN_STATE_MS: + stateResidency.totalTimeInStateMs = + pis.readLong(StateResidencyProto.TOTAL_TIME_IN_STATE_MS); + break; + + case (int) StateResidencyProto.TOTAL_STATE_ENTRY_COUNT: + stateResidency.totalStateEntryCount = + pis.readLong(StateResidencyProto.TOTAL_STATE_ENTRY_COUNT); + break; + + case (int) StateResidencyProto.LAST_ENTRY_TIMESTAMP_MS: + stateResidency.lastEntryTimestampMs = + pis.readLong(StateResidencyProto.LAST_ENTRY_TIMESTAMP_MS); + break; + + case ProtoInputStream.NO_MORE_FIELDS: + return stateResidency; + + default: + Slog.e(TAG, "Unhandled field in StateResidencyProto: " + + ProtoUtils.currentFieldToString(pis)); + break; + + } + } catch (WireTypeMismatchException wtme) { + Slog.e(TAG, "Wire Type mismatch in StateResidencyProto: " + + ProtoUtils.currentFieldToString(pis)); + } + } + } + public static void print(StateResidencyResult[] stateResidencyResult) { if (stateResidencyResult == null) return; @@ -98,17 +261,14 @@ public class ProtoStreamUtils { static class ChannelInfoUtils { public static void packProtoMessage(ChannelInfo[] channelInfo, ProtoOutputStream pos) { - long token; - if (channelInfo == null) return; for (int i = 0; i < channelInfo.length; i++) { - token = pos.start(PowerStatsServiceMeterProto.CHANNEL_INFO); + long token = pos.start(PowerStatsServiceMeterProto.CHANNEL_INFO); pos.write(ChannelInfoProto.CHANNEL_ID, channelInfo[i].channelId); pos.write(ChannelInfoProto.CHANNEL_NAME, channelInfo[i].channelName); pos.end(token); } - } public static void print(ChannelInfo[] channelInfo) { @@ -139,12 +299,10 @@ public class ProtoStreamUtils { public static void packProtoMessage(EnergyMeasurement[] energyMeasurement, ProtoOutputStream pos) { - long token; - if (energyMeasurement == null) return; for (int i = 0; i < energyMeasurement.length; i++) { - token = pos.start(PowerStatsServiceMeterProto.ENERGY_MEASUREMENT); + long token = pos.start(PowerStatsServiceMeterProto.ENERGY_MEASUREMENT); pos.write(EnergyMeasurementProto.CHANNEL_ID, energyMeasurement[i].channelId); pos.write(EnergyMeasurementProto.TIMESTAMP_MS, energyMeasurement[i].timestampMs); pos.write(EnergyMeasurementProto.ENERGY_UWS, energyMeasurement[i].energyUWs); @@ -155,7 +313,6 @@ public class ProtoStreamUtils { public static EnergyMeasurement[] unpackProtoMessage(byte[] data) throws IOException { final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data)); List<EnergyMeasurement> energyMeasurementList = new ArrayList<EnergyMeasurement>(); - long token; while (true) { try { @@ -163,8 +320,8 @@ public class ProtoStreamUtils { EnergyMeasurement energyMeasurement = new EnergyMeasurement(); if (nextField == (int) PowerStatsServiceMeterProto.ENERGY_MEASUREMENT) { - token = pis.start(PowerStatsServiceMeterProto.ENERGY_MEASUREMENT); - energyMeasurementList.add(unpackProtoMessage(pis)); + long token = pis.start(PowerStatsServiceMeterProto.ENERGY_MEASUREMENT); + energyMeasurementList.add(unpackEnergyMeasurementProto(pis)); pis.end(token); } else if (nextField == ProtoInputStream.NO_MORE_FIELDS) { return energyMeasurementList.toArray( @@ -180,7 +337,7 @@ public class ProtoStreamUtils { } } - private static EnergyMeasurement unpackProtoMessage(ProtoInputStream pis) + private static EnergyMeasurement unpackEnergyMeasurementProto(ProtoInputStream pis) throws IOException { EnergyMeasurement energyMeasurement = new EnergyMeasurement(); @@ -230,12 +387,10 @@ public class ProtoStreamUtils { static class EnergyConsumerIdUtils { public static void packProtoMessage(int[] energyConsumerId, ProtoOutputStream pos) { - long token; - if (energyConsumerId == null) return; for (int i = 0; i < energyConsumerId.length; i++) { - token = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_ID); + long token = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_ID); pos.write(EnergyConsumerIdProto.ENERGY_CONSUMER_ID, energyConsumerId[i]); pos.end(token); } @@ -267,12 +422,10 @@ public class ProtoStreamUtils { public static void packProtoMessage(EnergyConsumerResult[] energyConsumerResult, ProtoOutputStream pos) { - long token; - if (energyConsumerResult == null) return; for (int i = 0; i < energyConsumerResult.length; i++) { - token = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT); + long token = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT); pos.write(EnergyConsumerResultProto.ENERGY_CONSUMER_ID, energyConsumerResult[i].energyConsumerId); pos.write(EnergyConsumerResultProto.TIMESTAMP_MS, @@ -286,16 +439,14 @@ public class ProtoStreamUtils { final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data)); List<EnergyConsumerResult> energyConsumerResultList = new ArrayList<EnergyConsumerResult>(); - long token; - while (true) { try { int nextField = pis.nextField(); EnergyConsumerResult energyConsumerResult = new EnergyConsumerResult(); if (nextField == (int) PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT) { - token = pis.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT); - energyConsumerResultList.add(unpackProtoMessage(pis)); + long token = pis.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT); + energyConsumerResultList.add(unpackEnergyConsumerResultProto(pis)); pis.end(token); } else if (nextField == ProtoInputStream.NO_MORE_FIELDS) { return energyConsumerResultList.toArray( @@ -311,7 +462,7 @@ public class ProtoStreamUtils { } } - private static EnergyConsumerResult unpackProtoMessage(ProtoInputStream pis) + private static EnergyConsumerResult unpackEnergyConsumerResultProto(ProtoInputStream pis) throws IOException { EnergyConsumerResult energyConsumerResult = new EnergyConsumerResult(); diff --git a/services/core/java/com/android/server/powerstats/TimerTrigger.java b/services/core/java/com/android/server/powerstats/TimerTrigger.java index 4b5929552856..7cba00f9a669 100644 --- a/services/core/java/com/android/server/powerstats/TimerTrigger.java +++ b/services/core/java/com/android/server/powerstats/TimerTrigger.java @@ -29,7 +29,7 @@ public final class TimerTrigger extends PowerStatsLogTrigger { private static final String TAG = TimerTrigger.class.getSimpleName(); private static final boolean DEBUG = false; // TODO(b/166689029): Make configurable through global settings. - private static final long LOG_PERIOD_MS = 60 * 1000; + private static final long LOG_PERIOD_MS = 120 * 1000; private final Handler mHandler; @@ -40,7 +40,7 @@ public final class TimerTrigger extends PowerStatsLogTrigger { // LOG_PERIOD_MS. mHandler.postDelayed(mLogData, LOG_PERIOD_MS); if (DEBUG) Slog.d(TAG, "Received delayed message. Log rail data"); - logPowerStatsData(); + logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_TIMER); } }; diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index 35c9f9ae6683..3a08ddc6c405 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; 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 @@ class RollbackStore { 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 966be99a3edd..d65661771a84 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.BackgroundThread; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BinderCallsStats.ExportedCallStat; -import com.android.internal.os.KernelCpuSpeedReader; import com.android.internal.os.KernelCpuThreadReader; import com.android.internal.os.KernelCpuThreadReaderDiff; import com.android.internal.os.KernelCpuThreadReaderSettingsObserver; @@ -301,8 +300,6 @@ public class StatsPullAtomService extends SystemService { @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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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); @@ -614,6 +606,7 @@ public class StatsPullAtomService extends SystemService { return pullRoleHolderLocked(atomTag, data); } case FrameworkStatsLog.DANGEROUS_PERMISSION_STATE: + // fall-through - same call covers two cases case FrameworkStatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED: synchronized (mDangerousPermissionStateLock) { return pullDangerousPermissionStateLocked(atomTag, data); @@ -721,18 +714,6 @@ public class StatsPullAtomService extends SystemService { 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); @@ -792,7 +773,6 @@ public class StatsPullAtomService extends SystemService { mStatsCallbackImpl = new StatsPullAtomCallbackImpl(); registerBluetoothBytesTransfer(); registerKernelWakelock(); - registerCpuTimePerFreq(); registerCpuTimePerUid(); registerCpuCyclesPerUidCluster(); registerCpuTimePerUidFreq(); @@ -1464,32 +1444,6 @@ public class StatsPullAtomService extends SystemService { 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() @@ -2077,7 +2031,8 @@ public class StatsPullAtomService extends SystemService { metrics.pageTablesKb, metrics.kernelStackKb, metrics.totalIonKb, - metrics.unaccountedKb)); + metrics.unaccountedKb, + metrics.gpuTotalUsageKb)); return StatsManager.PULL_SUCCESS; } @@ -3014,7 +2969,7 @@ public class StatsPullAtomService extends SystemService { } int numPerms = pkg.requestedPermissions.length; - for (int permNum = 0; permNum < numPerms; permNum++) { + for (int permNum = 0; permNum < numPerms; permNum++) { String permName = pkg.requestedPermissions[permNum]; PermissionInfo permissionInfo; @@ -3027,10 +2982,6 @@ public class StatsPullAtomService extends SystemService { continue; } - if (permissionInfo.getProtection() != PROTECTION_DANGEROUS) { - continue; - } - if (permName.startsWith(COMMON_PERMISSION_PREFIX)) { permName = permName.substring(COMMON_PERMISSION_PREFIX.length()); } @@ -3042,15 +2993,17 @@ public class StatsPullAtomService extends SystemService { (pkg.requestedPermissionsFlags[permNum] & REQUESTED_PERMISSION_GRANTED) != 0, - permissionFlags); + permissionFlags, permissionInfo.getProtection() + | permissionInfo.getProtectionFlags()); } else { - // DangerousPermissionStateSampled atom. + // DangeorusPermissionStateSampled atom. e = FrameworkStatsLog.buildStatsEvent(atomTag, permName, pkg.applicationInfo.uid, (pkg.requestedPermissionsFlags[permNum] & REQUESTED_PERMISSION_GRANTED) != 0, - permissionFlags); + permissionFlags, permissionInfo.getProtection() + | permissionInfo.getProtectionFlags()); } pulledData.add(e); } @@ -3198,7 +3151,11 @@ public class StatsPullAtomService extends SystemService { int pullFaceSettingsLocked(int atomTag, List<StatsEvent> pulledData) { final long callingToken = Binder.clearCallingIdentity(); try { - List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers(); + UserManager manager = mContext.getSystemService(UserManager.class); + if (manager == null) { + return StatsManager.PULL_SKIP; + } + List<UserInfo> users = manager.getUsers(); int numUsers = users.size(); for (int userNum = 0; userNum < numUsers; userNum++) { int userId = users.get(userNum).getUserHandle().getIdentifier(); diff --git a/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java b/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java index 99fc7c11c5b3..1e80c4fe89fb 100644 --- a/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java +++ b/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java @@ -27,6 +27,7 @@ final class SystemMemoryUtil { static Metrics getMetrics() { int totalIonKb = (int) Debug.getIonHeapsSizeKb(); + int gpuTotalUsageKb = (int) Debug.getGpuTotalUsageKb(); long[] mInfos = new long[Debug.MEMINFO_COUNT]; Debug.getMemInfo(mInfos); @@ -62,6 +63,7 @@ final class SystemMemoryUtil { result.pageTablesKb = (int) mInfos[Debug.MEMINFO_PAGE_TABLES]; result.kernelStackKb = (int) mInfos[Debug.MEMINFO_KERNEL_STACK]; result.totalIonKb = totalIonKb; + result.gpuTotalUsageKb = gpuTotalUsageKb; result.unaccountedKb = (int) (mInfos[Debug.MEMINFO_TOTAL] - accountedKb); return result; } @@ -72,6 +74,7 @@ final class SystemMemoryUtil { public int pageTablesKb; public int kernelStackKb; public int totalIonKb; + public int gpuTotalUsageKb; public int unaccountedKb; } } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index 033bfa648f2f..d0c632350270 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 @@ package com.android.server.timezonedetector; 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; @@ -25,12 +27,15 @@ import android.app.timezonedetector.ITimeZoneDetectorService; import android.app.timezonedetector.ManualTimeZoneSuggestion; 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; @@ -162,9 +167,13 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub @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 = @@ -177,13 +186,22 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub @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); } @@ -311,6 +329,25 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub mHandler.post(() -> mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion)); } + boolean isGeoTimeZoneDetectionSupported() { + enforceManageTimeZoneDetectorPermission(); + + return isGeoLocationTimeZoneDetectionEnabled(mContext); + } + + boolean isLocationEnabled(@UserIdInt int userId) { + enforceManageTimeZoneDetectorPermission(); + + 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 protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) { diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java index 13b44566c66d..e965f55e49d7 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java @@ -15,13 +15,21 @@ */ package com.android.server.timezonedetector; +import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED; +import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED; +import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED; +import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_LOCATION_ENABLED; +import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED; +import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE; +import android.app.time.TimeZoneConfiguration; 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; @@ -43,6 +51,18 @@ class TimeZoneDetectorShellCommand extends ShellCommand { } switch (cmd) { + case SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED: + return runIsAutoDetectionEnabled(); + case SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED: + return runSetAutoDetectionEnabled(); + case SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED: + return runIsGeoDetectionSupported(); + case SHELL_COMMAND_IS_LOCATION_ENABLED: + return runIsLocationEnabled(); + case SHELL_COMMAND_IS_GEO_DETECTION_ENABLED: + return runIsGeoDetectionEnabled(); + case SHELL_COMMAND_SET_GEO_DETECTION_ENABLED: + return runSetGeoDetectionEnabled(); case SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE: return runSuggestGeolocationTimeZone(); case SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE: @@ -55,6 +75,59 @@ class TimeZoneDetectorShellCommand extends ShellCommand { } } + private int runIsAutoDetectionEnabled() { + final PrintWriter pw = getOutPrintWriter(); + int userId = UserHandle.USER_CURRENT; + boolean enabled = mInterface.getCapabilitiesAndConfig(userId) + .getConfiguration() + .isAutoDetectionEnabled(); + pw.println(enabled); + return 0; + } + + private int runIsGeoDetectionSupported() { + final PrintWriter pw = getOutPrintWriter(); + boolean enabled = mInterface.isGeoTimeZoneDetectionSupported(); + pw.println(enabled); + return 0; + } + + private int runIsLocationEnabled() { + final PrintWriter pw = getOutPrintWriter(); + int userId = UserHandle.USER_CURRENT; + boolean enabled = mInterface.isLocationEnabled(userId); + pw.println(enabled); + return 0; + } + + private int runIsGeoDetectionEnabled() { + final PrintWriter pw = getOutPrintWriter(); + int userId = UserHandle.USER_CURRENT; + boolean enabled = mInterface.getCapabilitiesAndConfig(userId) + .getConfiguration() + .isGeoDetectionEnabled(); + pw.println(enabled); + return 0; + } + + private int runSetAutoDetectionEnabled() { + boolean enabled = Boolean.parseBoolean(getNextArgRequired()); + int userId = UserHandle.USER_CURRENT; + TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder() + .setAutoDetectionEnabled(enabled) + .build(); + 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(userId, configuration) ? 0 : 1; + } + private int runSuggestGeolocationTimeZone() { return runSuggestTimeZone( () -> GeolocationTimeZoneSuggestion.parseCommandLineArg(this), @@ -96,6 +169,20 @@ class TimeZoneDetectorShellCommand extends ShellCommand { pw.println("Time Zone Detector (time_zone_detector) commands:"); pw.println(" help"); pw.println(" Print this help text."); + pw.printf(" %s\n", SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED); + pw.println(" Prints true/false according to the automatic tz detection setting"); + pw.printf(" %s true|false\n", SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED); + pw.println(" Sets the automatic tz detection setting."); + pw.printf(" %s\n", SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED); + pw.println(" Prints true/false according to whether geolocation time zone detection is" + + " supported on this device"); + pw.printf(" %s\n", SHELL_COMMAND_IS_LOCATION_ENABLED); + pw.println(" Prints true/false according to whether the master location toggle is" + + " enabled for the current user"); + pw.printf(" %s\n", SHELL_COMMAND_IS_GEO_DETECTION_ENABLED); + pw.println(" Prints true/false according to the geolocation tz detection setting"); + pw.printf(" %s true|false\n", SHELL_COMMAND_SET_GEO_DETECTION_ENABLED); + pw.println(" Sets the geolocation tz detection setting."); pw.printf(" %s <geolocation suggestion opts>\n", SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE); pw.printf(" %s <manual suggestion opts>\n", diff --git a/services/core/java/com/android/server/utils/Watchable.java b/services/core/java/com/android/server/utils/Watchable.java index 7c99274f3df2..f936693bd621 100644 --- a/services/core/java/com/android/server/utils/Watchable.java +++ b/services/core/java/com/android/server/utils/Watchable.java @@ -18,6 +18,10 @@ package com.android.server.utils; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Build; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; /** * Notify registered {@link Watcher}s when the content changes. @@ -41,6 +45,13 @@ public interface Watchable { public void unregisterObserver(@NonNull Watcher observer); /** + * Return true if the {@link Watcher) is a registered observer. + * @param observer A {@link Watcher} that might be registered + * @return true if the observer is registered with this {@link Watchable}. + */ + public boolean isRegisteredObserver(@NonNull Watcher observer); + + /** * Invokes {@link Watcher#onChange} on each registered observer. The method can be called * with the {@link Watchable} that generated the event. In a tree of {@link Watchable}s, this * is generally the first (deepest) {@link Watchable} to detect a change. @@ -48,4 +59,42 @@ public interface Watchable { * @param what The {@link Watchable} that generated the event. */ public void dispatchChange(@Nullable Watchable what); + + /** + * Return true if the field is tagged with @Watched + */ + private static boolean isWatched(Field f) { + for (Annotation a : f.getDeclaredAnnotations()) { + if (a.annotationType().equals(Watched.class)) { + return true; + } + } + return false; + } + + /** + * Verify that all @Watched {@link Watchable} attributes are being watched by this + * class. This requires reflection and only runs in engineering or user debug + * builds. + */ + static void verifyWatchedAttributes(Object base, Watcher observer) { + if (Build.IS_ENG || Build.IS_USERDEBUG) { + for (Field f : base.getClass().getDeclaredFields()) { + try { + final boolean flagged = isWatched(f); + final Object o = f.get(base); + final boolean watchable = o instanceof Watchable; + if (flagged && watchable) { + Watchable attr = (Watchable) f.get(base); + if (attr != null && !attr.isRegisteredObserver(observer)) { + throw new RuntimeException(f.getName() + " missing an observer"); + } + } + } catch (IllegalAccessException e) { + // The field is protected; ignore it. Other exceptions that may be thrown by + // Field.get() are allowed to roll up. + } + } + } + } } diff --git a/services/core/java/com/android/server/utils/WatchableImpl.java b/services/core/java/com/android/server/utils/WatchableImpl.java index 16400b186ab0..527db5402e74 100644 --- a/services/core/java/com/android/server/utils/WatchableImpl.java +++ b/services/core/java/com/android/server/utils/WatchableImpl.java @@ -66,6 +66,18 @@ public class WatchableImpl implements Watchable { } /** + * Return true if the {@link Watcher) is a registered observer. + * @param observer A {@link Watcher} that might be registered + * @return true if the observer is registered with this {@link Watchable}. + */ + @Override + public boolean isRegisteredObserver(@NonNull Watcher observer) { + synchronized (mObservers) { + return mObservers.contains(observer); + } + } + + /** * Return the number of registered observers. * * @return The number of registered observers. @@ -100,7 +112,7 @@ public class WatchableImpl implements Watchable { /** * Freeze the {@link Watchable}. - **/ + */ public void seal() { synchronized (mObservers) { mSealed = true; diff --git a/services/core/java/com/android/server/utils/Watched.java b/services/core/java/com/android/server/utils/Watched.java new file mode 100644 index 000000000000..4340d015119a --- /dev/null +++ b/services/core/java/com/android/server/utils/Watched.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.utils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation type to mark an attribute that is monitored for change detection and + * snapshot creation. + * TODO(b/176923052) Automate validation of @Watchable attributes. + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Watched { +} diff --git a/services/core/java/com/android/server/utils/WatchedArrayList.java b/services/core/java/com/android/server/utils/WatchedArrayList.java new file mode 100644 index 000000000000..bb0ba1329d86 --- /dev/null +++ b/services/core/java/com/android/server/utils/WatchedArrayList.java @@ -0,0 +1,416 @@ +/* + * 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.utils; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * WatchedArrayMap is an {@link android.util.ArrayMap} that can report changes to itself. If its + * values are {@link Watchable} then the WatchedArrayMap will also report changes to the values. + * A {@link Watchable} is notified only once, no matter how many times it is stored in the array. + * @param <E> The element type, stored in the array. + */ +public class WatchedArrayList<E> extends WatchableImpl + implements Snappable { + + // The storage + private final ArrayList<E> mStorage; + + // If true, the array is watching its children + private volatile boolean mWatching = false; + + // The local observer + private final Watcher mObserver = new Watcher() { + @Override + public void onChange(@Nullable Watchable what) { + WatchedArrayList.this.dispatchChange(what); + } + }; + + /** + * A convenience function called when the elements are added to or removed from the storage. + * The watchable is always {@link this}. + */ + private void onChanged() { + dispatchChange(this); + } + + /** + * A convenience function. Register the object if it is {@link Watchable} and if the + * array is currently watching. Note that the watching flag must be true if this + * function is to succeed. Also note that if this is called with the same object + * twice, <this> is only registered once. + */ + private void registerChild(Object o) { + if (mWatching && o instanceof Watchable) { + ((Watchable) o).registerObserver(mObserver); + } + } + + /** + * A convenience function. Unregister the object if it is {@link Watchable} and if the + * array is currently watching. This unconditionally removes the object from the + * registered list. + */ + private void unregisterChild(Object o) { + if (mWatching && o instanceof Watchable) { + ((Watchable) o).unregisterObserver(mObserver); + } + } + + /** + * A convenience function. Unregister the object if it is {@link Watchable}, if the + * array is currently watching, and if there are no other instances of this object in + * the storage. Note that the watching flag must be true if this function is to + * succeed. The object must already have been removed from the storage before this + * method is called. + */ + private void unregisterChildIf(Object o) { + if (mWatching && o instanceof Watchable) { + if (!mStorage.contains(o)) { + ((Watchable) o).unregisterObserver(mObserver); + } + } + } + + /** + * Register a {@link Watcher} with the array. If this is the first Watcher than any + * array values that are {@link Watchable} are registered to the array itself. + */ + @Override + public void registerObserver(@NonNull Watcher observer) { + super.registerObserver(observer); + if (registeredObserverCount() == 1) { + // The watching flag must be set true before any children are registered. + mWatching = true; + final int end = mStorage.size(); + for (int i = 0; i < end; i++) { + registerChild(mStorage.get(i)); + } + } + } + + /** + * Unregister a {@link Watcher} from the array. If this is the last Watcher than any + * array values that are {@link Watchable} are unregistered to the array itself. + */ + @Override + public void unregisterObserver(@NonNull Watcher observer) { + super.unregisterObserver(observer); + if (registeredObserverCount() == 0) { + final int end = mStorage.size(); + for (int i = 0; i < end; i++) { + unregisterChild(mStorage.get(i)); + } + // The watching flag must be true while children are unregistered. + mWatching = false; + } + } + + /** + * Create a new empty {@link WatchedArrayList}. The default capacity of an array map + * is 0, and will grow once items are added to it. + */ + public WatchedArrayList() { + this(0); + } + + /** + * Create a new {@link WatchedArrayList} with a given initial capacity. + */ + public WatchedArrayList(int capacity) { + mStorage = new ArrayList<E>(capacity); + } + + /** + * Create a new {@link WatchedArrayList} with the content of the collection. + */ + public WatchedArrayList(@Nullable Collection<? extends E> c) { + mStorage = new ArrayList<E>(); + if (c != null) { + // There is no need to register children because the WatchedArrayList starts + // life unobserved. + mStorage.addAll(c); + } + } + + /** + * Create a {@link WatchedArrayList} from an {@link ArrayList} + */ + public WatchedArrayList(@NonNull ArrayList<E> c) { + mStorage = new ArrayList<>(c); + } + + /** + * Create a {@link WatchedArrayList} from an {@link WatchedArrayList} + */ + public WatchedArrayList(@NonNull WatchedArrayList<E> c) { + mStorage = new ArrayList<>(c.mStorage); + } + + /** + * Make <this> a copy of src. Any data in <this> is discarded. + */ + public void copyFrom(@NonNull ArrayList<E> src) { + clear(); + final int end = src.size(); + mStorage.ensureCapacity(end); + for (int i = 0; i < end; i++) { + add(src.get(i)); + } + } + + /** + * Make dst a copy of <this>. Any previous data in dst is discarded. + */ + public void copyTo(@NonNull ArrayList<E> dst) { + dst.clear(); + final int end = size(); + dst.ensureCapacity(end); + for (int i = 0; i < end; i++) { + dst.add(get(i)); + } + } + + /** + * Return the underlying storage. This breaks the wrapper but is necessary when + * passing the array to distant methods. + */ + public ArrayList<E> untrackedStorage() { + return mStorage; + } + + /** + * Append the specified element to the end of the list + */ + public boolean add(E value) { + final boolean result = mStorage.add(value); + registerChild(value); + onChanged(); + return result; + } + + /** + * Insert the element into the list + */ + public void add(int index, E value) { + mStorage.add(index, value); + registerChild(value); + onChanged(); + } + + /** + * Append the elements of the collection to the list. + */ + public boolean addAll(Collection<? extends E> c) { + if (c.size() > 0) { + for (E e: c) { + mStorage.add(e); + } + onChanged(); + return true; + } else { + return false; + } + } + + /** + * Insert the elements of the collection into the list at the index. + */ + public boolean addAll(int index, Collection<? extends E> c) { + if (c.size() > 0) { + for (E e: c) { + mStorage.add(index++, e); + } + onChanged(); + return true; + } else { + return false; + } + } + + + /** + * Remove all elements from the list. + */ + public void clear() { + // The storage cannot be simply cleared. Each element in the storage must be + // unregistered. Deregistration is only needed if the array is actually + // watching. + if (mWatching) { + final int end = mStorage.size(); + for (int i = 0; i < end; i++) { + unregisterChild(mStorage.get(i)); + } + } + mStorage.clear(); + onChanged(); + } + + /** + * Return true if the object is in the array. + */ + public boolean contains(Object o) { + return mStorage.contains(o); + } + + /** + * Ensure capacity. + */ + public void ensureCapacity(int min) { + mStorage.ensureCapacity(min); + } + + /** + * Retrieve the element at the specified index. + */ + public E get(int index) { + return mStorage.get(index); + } + + /** + * Return the index of the object. -1 is returned if the object is not in the list. + */ + public int indexOf(Object o) { + return mStorage.indexOf(o); + } + + /** + * True if the list has no elements + */ + public boolean isEmpty() { + return mStorage.isEmpty(); + } + + /** + * Return the index of the last occurrence of the object. + */ + public int lastIndexOf(Object o) { + return mStorage.lastIndexOf(o); + } + + /** + * Remove and return the element at the specified position. + */ + public E remove(int index) { + final E result = mStorage.remove(index); + unregisterChildIf(result); + onChanged(); + return result; + } + + /** + * Remove the first occurrence of the object in the list. Return true if the object + * was actually in the list and false otherwise. + */ + public boolean remove(Object o) { + if (mStorage.remove(o)) { + unregisterChildIf(o); + onChanged(); + return true; + } + return false; + } + + /** + * Replace the object at the index. + */ + public E set(int index, E value) { + final E result = mStorage.set(index, value); + if (value != result) { + unregisterChildIf(result); + registerChild(value); + onChanged(); + } + return result; + } + + /** + * Return the number of elements in the list. + */ + public int size() { + return mStorage.size(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof WatchedArrayList) { + WatchedArrayList w = (WatchedArrayList) o; + return mStorage.equals(w.mStorage); + } else { + return false; + } + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return mStorage.hashCode(); + } + + /** + * Create a copy of the array. If the element is a subclass of Snapper then the copy + * contains snapshots of the elements. Otherwise the copy contains references to the + * elements. The returned snapshot is immutable. + * @return A new array whose elements are the elements of <this>. + */ + public WatchedArrayList<E> snapshot() { + WatchedArrayList<E> l = new WatchedArrayList<>(size()); + snapshot(l, this); + return l; + } + + /** + * Make <this> a snapshot of the argument. Note that <this> is immutable when the + * method returns. <this> must be empty when the function is called. + * @param r The source array, which is copied into <this> + */ + public void snapshot(@NonNull WatchedArrayList<E> r) { + snapshot(this, r); + } + + /** + * Make the destination a copy of the source. If the element is a subclass of Snapper then the + * copy contains snapshots of the elements. Otherwise the copy contains references to the + * elements. The destination must be initially empty. Upon return, the destination is + * immutable. + * @param dst The destination array. It must be empty. + * @param src The source array. It is not modified. + */ + public static <E> void snapshot(@NonNull WatchedArrayList<E> dst, + @NonNull WatchedArrayList<E> src) { + if (dst.size() != 0) { + throw new IllegalArgumentException("snapshot destination is not empty"); + } + final int end = src.size(); + dst.mStorage.ensureCapacity(end); + for (int i = 0; i < end; i++) { + final E val = Snapshots.maybeSnapshot(src.get(i)); + dst.add(i, val); + } + dst.seal(); + } +} diff --git a/services/core/java/com/android/server/utils/WatchedArrayMap.java b/services/core/java/com/android/server/utils/WatchedArrayMap.java index e8065f140af7..7c1cde8502bd 100644 --- a/services/core/java/com/android/server/utils/WatchedArrayMap.java +++ b/services/core/java/com/android/server/utils/WatchedArrayMap.java @@ -160,10 +160,48 @@ public class WatchedArrayMap<K, V> extends WatchableImpl } /** + * Create a {@link WatchedArrayMap} from an {@link ArrayMap} + */ + public WatchedArrayMap(@NonNull ArrayMap<K, V> c) { + mStorage = new ArrayMap<>(c); + } + + /** + * Create a {@link WatchedArrayMap} from an {@link WatchedArrayMap} + */ + public WatchedArrayMap(@NonNull WatchedArrayMap<K, V> c) { + mStorage = new ArrayMap<>(c.mStorage); + } + + /** + * Make <this> a copy of src. Any data in <this> is discarded. + */ + public void copyFrom(@NonNull ArrayMap<K, V> src) { + clear(); + final int end = src.size(); + mStorage.ensureCapacity(end); + for (int i = 0; i < end; i++) { + put(src.keyAt(i), src.valueAt(i)); + } + } + + /** + * Make dst a copy of <this>. Any previous data in dst is discarded. + */ + public void copyTo(@NonNull ArrayMap<K, V> dst) { + dst.clear(); + final int end = size(); + dst.ensureCapacity(end); + for (int i = 0; i < end; i++) { + dst.put(keyAt(i), valueAt(i)); + } + } + + /** * Return the underlying storage. This breaks the wrapper but is necessary when * passing the array to distant methods. */ - public ArrayMap untrackedMap() { + public ArrayMap<K, V> untrackedStorage() { return mStorage; } @@ -213,7 +251,7 @@ public class WatchedArrayMap<K, V> extends WatchableImpl * {@inheritDoc} */ @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof WatchedArrayMap) { WatchedArrayMap w = (WatchedArrayMap) o; return mStorage.equals(w.mStorage); @@ -401,6 +439,15 @@ public class WatchedArrayMap<K, V> extends WatchableImpl } /** + * Make <this> a snapshot of the argument. Note that <this> is immutable when the + * method returns. <this> must be empty when the function is called. + * @param r The source array, which is copied into <this> + */ + public void snapshot(@NonNull WatchedArrayMap<K, V> r) { + snapshot(this, r); + } + + /** * Make the destination a copy of the source. If the element is a subclass of Snapper then the * copy contains snapshots of the elements. Otherwise the copy contains references to the * elements. The destination must be initially empty. Upon return, the destination is @@ -414,6 +461,7 @@ public class WatchedArrayMap<K, V> extends WatchableImpl throw new IllegalArgumentException("snapshot destination is not empty"); } final int end = src.size(); + dst.mStorage.ensureCapacity(end); for (int i = 0; i < end; i++) { final V val = Snapshots.maybeSnapshot(src.valueAt(i)); final K key = src.keyAt(i); diff --git a/services/core/java/com/android/server/utils/WatchedArraySet.java b/services/core/java/com/android/server/utils/WatchedArraySet.java new file mode 100644 index 000000000000..5070dd1675d3 --- /dev/null +++ b/services/core/java/com/android/server/utils/WatchedArraySet.java @@ -0,0 +1,434 @@ +/* + * 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.utils; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.ArraySet; + +/** + * WatchedArraySet is an {@link android.util.ArraySet} that can report changes to itself. If its + * values are {@link Watchable} then the WatchedArraySet will also report changes to the values. + * A {@link Watchable} is notified only once, no matter how many times it is stored in the array. + * @param <E> The element type + */ +public class WatchedArraySet<E> extends WatchableImpl + implements Snappable { + + // The storage + private final ArraySet<E> mStorage; + + // If true, the array is watching its children + private volatile boolean mWatching = false; + + // The local observer + private final Watcher mObserver = new Watcher() { + @Override + public void onChange(@Nullable Watchable what) { + WatchedArraySet.this.dispatchChange(what); + } + }; + + /** + * A convenience function called when the elements are added to or removed from the storage. + * The watchable is always {@link this}. + */ + private void onChanged() { + dispatchChange(this); + } + + /** + * A convenience function. Register the object if it is {@link Watchable} and if the + * array is currently watching. Note that the watching flag must be true if this + * function is to succeed. Also note that if this is called with the same object + * twice, <this> is only registered once. + */ + private void registerChild(Object o) { + if (mWatching && o instanceof Watchable) { + ((Watchable) o).registerObserver(mObserver); + } + } + + /** + * A convenience function. Unregister the object if it is {@link Watchable} and if the + * array is currently watching. This unconditionally removes the object from the + * registered list. + */ + private void unregisterChild(Object o) { + if (mWatching && o instanceof Watchable) { + ((Watchable) o).unregisterObserver(mObserver); + } + } + + /** + * A convenience function. Unregister the object if it is {@link Watchable}, if the + * array is currently watching, and if there are no other instances of this object in + * the storage. Note that the watching flag must be true if this function is to + * succeed. The object must already have been removed from the storage before this + * method is called. + */ + private void unregisterChildIf(Object o) { + if (mWatching && o instanceof Watchable) { + if (!mStorage.contains(o)) { + ((Watchable) o).unregisterObserver(mObserver); + } + } + } + + /** + * Register a {@link Watcher} with the array. If this is the first Watcher than any + * array values that are {@link Watchable} are registered to the array itself. + */ + @Override + public void registerObserver(@NonNull Watcher observer) { + super.registerObserver(observer); + if (registeredObserverCount() == 1) { + // The watching flag must be set true before any children are registered. + mWatching = true; + final int end = mStorage.size(); + for (int i = 0; i < end; i++) { + registerChild(mStorage.valueAt(i)); + } + } + } + + /** + * Unregister a {@link Watcher} from the array. If this is the last Watcher than any + * array values that are {@link Watchable} are unregistered to the array itself. + */ + @Override + public void unregisterObserver(@NonNull Watcher observer) { + super.unregisterObserver(observer); + if (registeredObserverCount() == 0) { + final int end = mStorage.size(); + for (int i = 0; i < end; i++) { + unregisterChild(mStorage.valueAt(i)); + } + // The watching flag must be true while children are unregistered. + mWatching = false; + } + } + + /** + * Create a new empty {@link WatchedArraySet}. The default capacity of an array map + * is 0, and will grow once items are added to it. + */ + public WatchedArraySet() { + this(0, false); + } + + /** + * Create a new {@link WatchedArraySet} with a given initial capacity. + */ + public WatchedArraySet(int capacity) { + this(capacity, false); + } + + /** {@hide} */ + public WatchedArraySet(int capacity, boolean identityHashCode) { + mStorage = new ArraySet<E>(capacity, identityHashCode); + } + + /** + * Create a new {@link WatchedArraySet} with items from the given array + */ + public WatchedArraySet(@Nullable E[] array) { + mStorage = new ArraySet(array); + } + + /** + * Create a {@link WatchedArraySet} from an {@link ArraySet} + */ + public WatchedArraySet(@NonNull ArraySet<E> c) { + mStorage = new ArraySet<>(c); + } + + /** + * Create a {@link WatchedArraySet} from an {@link WatchedArraySet} + */ + public WatchedArraySet(@NonNull WatchedArraySet<E> c) { + mStorage = new ArraySet<>(c.mStorage); + } + + /** + * Make <this> a copy of src. Any data in <this> is discarded. + */ + public void copyFrom(@NonNull ArraySet<E> src) { + clear(); + final int end = src.size(); + mStorage.ensureCapacity(end); + for (int i = 0; i < end; i++) { + add(src.valueAt(i)); + } + } + + /** + * Make dst a copy of <this>. Any previous data in dst is discarded. + */ + public void copyTo(@NonNull ArraySet<E> dst) { + dst.clear(); + final int end = size(); + dst.ensureCapacity(end); + for (int i = 0; i < end; i++) { + dst.add(valueAt(i)); + } + } + + /** + * Return the underlying storage. This breaks the wrapper but is necessary when + * passing the array to distant methods. + */ + public ArraySet<E> untrackedStorage() { + return mStorage; + } + + /** + * Make the array map empty. All storage is released. + */ + public void clear() { + // The storage cannot be simply cleared. Each element in the storage must be + // unregistered. Deregistration is only needed if the array is actually + // watching. + if (mWatching) { + final int end = mStorage.size(); + for (int i = 0; i < end; i++) { + unregisterChild(mStorage.valueAt(i)); + } + } + mStorage.clear(); + onChanged(); + } + + /** + * Check whether a value exists in the set. + * + * @param key The value to search for. + * @return Returns true if the value exists, else false. + */ + public boolean contains(Object key) { + return mStorage.contains(key); + } + + /** + * Returns the index of a value in the set. + * + * @param key The value to search for. + * @return Returns the index of the value if it exists, else a negative integer. + */ + public int indexOf(Object key) { + return mStorage.indexOf(key); + } + + /** + * Return the value at the given index in the array. + * + * <p>For indices outside of the range <code>0...size()-1</code>, an + * {@link ArrayIndexOutOfBoundsException} is thrown.</p> + * + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @return Returns the value stored at the given index. + */ + public E valueAt(int index) { + return mStorage.valueAt(index); + } + + /** + * Return true if the array map contains no items. + */ + public boolean isEmpty() { + return mStorage.isEmpty(); + } + + /** + * Adds the specified object to this set. The set is not modified if it + * already contains the object. + * + * @param value the object to add. + * @return {@code true} if this set is modified, {@code false} otherwise. + */ + public boolean add(E value) { + final boolean result = mStorage.add(value); + registerChild(value); + onChanged(); + return result; + } + + /** + * Special fast path for appending items to the end of the array without validation. + * The array must already be large enough to contain the item. + * @hide + */ + public void append(E value) { + mStorage.append(value); + registerChild(value); + onChanged(); + } + + /** + * Perform a {@link #add(Object)} of all values in <var>array</var> + * @param array The array whose contents are to be retrieved. + */ + public void addAll(ArraySet<? extends E> array) { + final int end = array.size(); + for (int i = 0; i < end; i++) { + add(array.valueAt(i)); + } + } + + /** + * Perform a {@link #add(Object)} of all values in <var>array</var> + * @param array The array whose contents are to be retrieved. + */ + public void addAll(WatchedArraySet<? extends E> array) { + final int end = array.size(); + for (int i = 0; i < end; i++) { + add(array.valueAt(i)); + } + } + + /** + * Removes the specified object from this set. + * + * @param o the object to remove. + * @return {@code true} if this set was modified, {@code false} otherwise. + */ + public boolean remove(Object o) { + if (mStorage.remove(o)) { + unregisterChildIf(o); + onChanged(); + return true; + } + return false; + } + + /** + * Remove the key/value mapping at the given index. + * + * <p>For indices outside of the range <code>0...size()-1</code>, an + * {@link ArrayIndexOutOfBoundsException} is thrown.</p> + * + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @return Returns the value that was stored at this index. + */ + public E removeAt(int index) { + final E result = mStorage.removeAt(index); + unregisterChildIf(result); + onChanged(); + return result; + } + + /** + * Perform a {@link #remove(Object)} of all values in <var>array</var> + * @param array The array whose contents are to be removed. + */ + public boolean removeAll(ArraySet<? extends E> array) { + final int end = array.size(); + boolean any = false; + for (int i = 0; i < end; i++) { + any = remove(array.valueAt(i)) || any; + } + return any; + } + + /** + * Return the number of items in this array map. + */ + public int size() { + return mStorage.size(); + } + + /** + * {@inheritDoc} + * + * <p>This implementation returns false if the object is not a set, or + * if the sets have different sizes. Otherwise, for each value in this + * set, it checks to make sure the value also exists in the other set. + * If any value doesn't exist, the method returns false; otherwise, it + * returns true. + */ + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof WatchedArraySet) { + return mStorage.equals(((WatchedArraySet) object).mStorage); + } else { + return mStorage.equals(object); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return mStorage.hashCode(); + } + + /** + * {@inheritDoc} + * + * <p>This implementation composes a string by iterating over its values. If + * this set contains itself as a value, the string "(this Set)" + * will appear in its place. + */ + @Override + public String toString() { + return mStorage.toString(); + } + + /** + * Create a copy of the array. If the element is a subclass of Snapper then the copy + * contains snapshots of the elements. Otherwise the copy contains references to the + * elements. The returned snapshot is immutable. + * @return A new array whose elements are the elements of <this>. + */ + public WatchedArraySet<E> snapshot() { + WatchedArraySet<E> l = new WatchedArraySet<>(); + snapshot(l, this); + return l; + } + + /** + * Make <this> a snapshot of the argument. Note that <this> is immutable when the + * method returns. <this> must be empty when the function is called. + * @param r The source array, which is copied into <this> + */ + public void snapshot(@NonNull WatchedArraySet<E> r) { + snapshot(this, r); + } + + /** + * Make the destination a copy of the source. If the element is a subclass of Snapper then the + * copy contains snapshots of the elements. Otherwise the copy contains references to the + * elements. The destination must be initially empty. Upon return, the destination is + * immutable. + * @param dst The destination array. It must be empty. + * @param src The source array. It is not modified. + */ + public static <E> void snapshot(@NonNull WatchedArraySet<E> dst, + @NonNull WatchedArraySet<E> src) { + if (dst.size() != 0) { + throw new IllegalArgumentException("snapshot destination is not empty"); + } + final int end = src.size(); + dst.mStorage.ensureCapacity(end); + for (int i = 0; i < end; i++) { + final E val = Snapshots.maybeSnapshot(src.valueAt(i)); + dst.append(val); + } + dst.seal(); + } +} diff --git a/services/core/java/com/android/server/utils/WatchedSparseArray.java b/services/core/java/com/android/server/utils/WatchedSparseArray.java index 6797c6eb7801..9b99b9176d19 100644 --- a/services/core/java/com/android/server/utils/WatchedSparseArray.java +++ b/services/core/java/com/android/server/utils/WatchedSparseArray.java @@ -143,6 +143,13 @@ public class WatchedSparseArray<E> extends WatchableImpl } /** + * Create a {@link WatchedSparseArray} from a {@link SparseArray} + */ + public WatchedSparseArray(@NonNull SparseArray<E> c) { + mStorage = c.clone(); + } + + /** * The copy constructor does not copy the watcher data. */ public WatchedSparseArray(@NonNull WatchedSparseArray<E> r) { @@ -150,6 +157,36 @@ public class WatchedSparseArray<E> extends WatchableImpl } /** + * Make <this> a copy of src. Any data in <this> is discarded. + */ + public void copyFrom(@NonNull SparseArray<E> src) { + clear(); + final int end = src.size(); + for (int i = 0; i < end; i++) { + put(src.keyAt(i), src.valueAt(i)); + } + } + + /** + * Make dst a copy of <this>. Any previous data in dst is discarded. + */ + public void copyTo(@NonNull SparseArray<E> dst) { + dst.clear(); + final int end = size(); + for (int i = 0; i < end; i++) { + dst.put(keyAt(i), valueAt(i)); + } + } + + /** + * Return the underlying storage. This breaks the wrapper but is necessary when + * passing the array to distant methods. + */ + public SparseArray<E> untrackedStorage() { + return mStorage; + } + + /** * Returns true if the key exists in the array. This is equivalent to * {@link #indexOfKey(int)} >= 0. * @@ -390,6 +427,21 @@ public class WatchedSparseArray<E> extends WatchableImpl onChanged(); } + @Override + public int hashCode() { + return mStorage.hashCode(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof WatchedSparseArray) { + WatchedSparseArray w = (WatchedSparseArray) o; + return mStorage.equals(w.mStorage); + } else { + return false; + } + } + /** * <p>This implementation composes a string by iterating over its mappings. If * this map contains itself as a value, the string "(this Map)" @@ -407,12 +459,21 @@ public class WatchedSparseArray<E> extends WatchableImpl * @return A new array whose elements are the elements of <this>. */ public WatchedSparseArray<E> snapshot() { - WatchedSparseArray<E> l = new WatchedSparseArray<>(); + WatchedSparseArray<E> l = new WatchedSparseArray<>(size()); snapshot(l, this); return l; } /** + * Make <this> a snapshot of the argument. Note that <this> is immutable when the + * method returns. <this> must be empty when the function is called. + * @param r The source array, which is copied into <this> + */ + public void snapshot(@NonNull WatchedSparseArray<E> r) { + snapshot(this, r); + } + + /** * Make the destination a copy of the source. If the element is a subclass of Snapper then the * copy contains snapshots of the elements. Otherwise the copy contains references to the * elements. The destination must be initially empty. Upon return, the destination is diff --git a/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java b/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java index b845eea168a5..772a8d07cffb 100644 --- a/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java +++ b/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java @@ -17,6 +17,7 @@ package com.android.server.utils; import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.SparseBooleanArray; /** @@ -53,6 +54,13 @@ public class WatchedSparseBooleanArray extends WatchableImpl } /** + * Create a {@link WatchedSparseBooleanArray} from a {@link SparseBooleanArray} + */ + public WatchedSparseBooleanArray(@NonNull SparseBooleanArray c) { + mStorage = c.clone(); + } + + /** * The copy constructor does not copy the watcher data. */ public WatchedSparseBooleanArray(@NonNull WatchedSparseBooleanArray r) { @@ -60,6 +68,36 @@ public class WatchedSparseBooleanArray extends WatchableImpl } /** + * Make <this> a copy of src. Any data in <this> is discarded. + */ + public void copyFrom(@NonNull SparseBooleanArray src) { + clear(); + final int end = src.size(); + for (int i = 0; i < end; i++) { + put(src.keyAt(i), src.valueAt(i)); + } + } + + /** + * Make dst a copy of <this>. Any previous data in dst is discarded. + */ + public void copyTo(@NonNull SparseBooleanArray dst) { + dst.clear(); + final int end = size(); + for (int i = 0; i < end; i++) { + dst.put(keyAt(i), valueAt(i)); + } + } + + /** + * Return the underlying storage. This breaks the wrapper but is necessary when + * passing the array to distant methods. + */ + public SparseBooleanArray untrackedStorage() { + return mStorage; + } + + /** * Gets the boolean mapped from the specified key, or <code>false</code> * if no such mapping has been made. */ @@ -99,10 +137,10 @@ public class WatchedSparseBooleanArray extends WatchableImpl * was one. */ public void put(int key, boolean value) { - if (mStorage.get(key) != value) { - mStorage.put(key, value); - onChanged(); - } + // There is no fast way to know if the key exists with the input value, so this + // method always notifies change listeners. + mStorage.put(key, value); + onChanged(); } /** @@ -219,8 +257,13 @@ public class WatchedSparseBooleanArray extends WatchableImpl } @Override - public boolean equals(Object that) { - return this == that || mStorage.equals(that); + public boolean equals(@Nullable Object o) { + if (o instanceof WatchedSparseBooleanArray) { + WatchedSparseBooleanArray w = (WatchedSparseBooleanArray) o; + return mStorage.equals(w.mStorage); + } else { + return false; + } } /** @@ -249,13 +292,26 @@ public class WatchedSparseBooleanArray extends WatchableImpl * @param r The source array, which is copied into <this> */ public void snapshot(@NonNull WatchedSparseBooleanArray r) { - if (size() != 0) { + snapshot(this, r); + } + + /** + * Make the destination a copy of the source. If the element is a subclass of Snapper then the + * copy contains snapshots of the elements. Otherwise the copy contains references to the + * elements. The destination must be initially empty. Upon return, the destination is + * immutable. + * @param dst The destination array. It must be empty. + * @param src The source array. It is not modified. + */ + public static void snapshot(@NonNull WatchedSparseBooleanArray dst, + @NonNull WatchedSparseBooleanArray src) { + if (dst.size() != 0) { throw new IllegalArgumentException("snapshot destination is not empty"); } - final int end = r.size(); + final int end = src.size(); for (int i = 0; i < end; i++) { - put(r.keyAt(i), r.valueAt(i)); + dst.put(src.keyAt(i), src.valueAt(i)); } - seal(); + dst.seal(); } } diff --git a/services/core/java/com/android/server/utils/WatchedSparseIntArray.java b/services/core/java/com/android/server/utils/WatchedSparseIntArray.java new file mode 100644 index 000000000000..72705bf24199 --- /dev/null +++ b/services/core/java/com/android/server/utils/WatchedSparseIntArray.java @@ -0,0 +1,323 @@ +/* + * 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.utils; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.SparseIntArray; + +/** + * A watched variant of SparseIntArray. Changes to the array are notified to + * registered {@link Watcher}s. + */ +public class WatchedSparseIntArray extends WatchableImpl + implements Snappable { + + // The storage + private final SparseIntArray mStorage; + + // A private convenience function + private void onChanged() { + dispatchChange(this); + } + + /** + * Creates a new WatchedSparseIntArray containing no mappings. + */ + public WatchedSparseIntArray() { + mStorage = new SparseIntArray(); + } + + /** + * Creates a new WatchedSparseIntArray containing no mappings that + * will not require any additional memory allocation to store the + * specified number of mappings. If you supply an initial capacity of + * 0, the sparse array will be initialized with a light-weight + * representation not requiring any additional array allocations. + */ + public WatchedSparseIntArray(int initialCapacity) { + mStorage = new SparseIntArray(initialCapacity); + } + + /** + * Create a {@link WatchedSparseIntArray} from a {@link SparseIntArray} + */ + public WatchedSparseIntArray(@NonNull SparseIntArray c) { + mStorage = c.clone(); + } + + /** + * The copy constructor does not copy the watcher data. + */ + public WatchedSparseIntArray(@NonNull WatchedSparseIntArray r) { + mStorage = r.mStorage.clone(); + } + + /** + * Make <this> a copy of src. Any data in <this> is discarded. + */ + public void copyFrom(@NonNull SparseIntArray src) { + clear(); + final int end = src.size(); + for (int i = 0; i < end; i++) { + put(src.keyAt(i), src.valueAt(i)); + } + } + + /** + * Make dst a copy of <this>. Any previous data in dst is discarded. + */ + public void copyTo(@NonNull SparseIntArray dst) { + dst.clear(); + final int end = size(); + for (int i = 0; i < end; i++) { + dst.put(keyAt(i), valueAt(i)); + } + } + + /** + * Return the underlying storage. This breaks the wrapper but is necessary when + * passing the array to distant methods. + */ + public SparseIntArray untrackedStorage() { + return mStorage; + } + + /** + * Gets the boolean mapped from the specified key, or <code>false</code> + * if no such mapping has been made. + */ + public int get(int key) { + return mStorage.get(key); + } + + /** + * Gets the boolean mapped from the specified key, or the specified value + * if no such mapping has been made. + */ + public int get(int key, int valueIfKeyNotFound) { + return mStorage.get(key, valueIfKeyNotFound); + } + + /** + * Removes the mapping from the specified key, if there was any. + */ + public void delete(int key) { + // This code ensures that onChanged is called only if the key is actually + // present. + final int index = mStorage.indexOfKey(key); + if (index >= 0) { + mStorage.removeAt(index); + onChanged(); + } + } + + /** + * Removes the mapping at the specified index. + */ + public void removeAt(int index) { + mStorage.removeAt(index); + onChanged(); + } + + /** + * Adds a mapping from the specified key to the specified value, + * replacing the previous mapping from the specified key if there + * was one. + */ + public void put(int key, int value) { + // There is no fast way to know if the key exists with the input value, so this + // method always notifies change listeners. + mStorage.put(key, value); + onChanged(); + } + + /** + * Returns the number of key-value mappings that this SparseIntArray + * currently stores. + */ + public int size() { + return mStorage.size(); + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the key from the <code>index</code>th key-value mapping that this + * SparseIntArray stores. + * + * <p>The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., <code>keyAt(0)</code> will return the + * smallest key and <code>keyAt(size()-1)</code> will return the largest + * key.</p> + * + * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for + * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an + * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} and later.</p> + */ + public int keyAt(int index) { + return mStorage.keyAt(index); + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the value from the <code>index</code>th key-value mapping that this + * SparseIntArray stores. + * + * <p>The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * <code>valueAt(0)</code> will return the value associated with the + * smallest key and <code>valueAt(size()-1)</code> will return the value + * associated with the largest key.</p> + * + * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for + * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an + * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} and later.</p> + */ + public int valueAt(int index) { + return mStorage.valueAt(index); + } + + /** + * Directly set the value at a particular index. + * + * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for + * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an + * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} and later.</p> + */ + public void setValueAt(int index, int value) { + if (mStorage.valueAt(index) != value) { + mStorage.setValueAt(index, value); + onChanged(); + } + } + + /** + * Returns the index for which {@link #keyAt} would return the + * specified key, or a negative number if the specified + * key is not mapped. + */ + public int indexOfKey(int key) { + return mStorage.indexOfKey(key); + } + + /** + * Returns an index for which {@link #valueAt} would return the + * specified key, or a negative number if no keys map to the + * specified value. + * Beware that this is a linear search, unlike lookups by key, + * and that multiple keys can map to the same value and this will + * find only one of them. + */ + public int indexOfValue(int value) { + return mStorage.indexOfValue(value); + } + + /** + * Removes all key-value mappings from this SparseIntArray. + */ + public void clear() { + final int count = size(); + mStorage.clear(); + if (count > 0) { + onChanged(); + } + } + + /** + * Puts a key/value pair into the array, optimizing for the case where + * the key is greater than all existing keys in the array. + */ + public void append(int key, int value) { + mStorage.append(key, value); + onChanged(); + } + + /** + * Provides a copy of keys. + **/ + public int[] copyKeys() { + return mStorage.copyKeys(); + } + + @Override + public int hashCode() { + return mStorage.hashCode(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof WatchedSparseIntArray) { + WatchedSparseIntArray w = (WatchedSparseIntArray) o; + return mStorage.equals(w.mStorage); + } else { + return false; + } + } + + /** + * {@inheritDoc} + * + * <p>This implementation composes a string by iterating over its mappings. + */ + @Override + public String toString() { + return mStorage.toString(); + } + + /** + * Create a snapshot. The snapshot does not include any {@link Watchable} + * information. + */ + public WatchedSparseIntArray snapshot() { + WatchedSparseIntArray l = new WatchedSparseIntArray(this); + l.seal(); + return l; + } + + /** + * Make <this> a snapshot of the argument. Note that <this> is immutable when the + * method returns. <this> must be empty when the function is called. + * @param r The source array, which is copied into <this> + */ + public void snapshot(@NonNull WatchedSparseIntArray r) { + snapshot(this, r); + } + + /** + * Make the destination a copy of the source. If the element is a subclass of Snapper then the + * copy contains snapshots of the elements. Otherwise the copy contains references to the + * elements. The destination must be initially empty. Upon return, the destination is + * immutable. + * @param dst The destination array. It must be empty. + * @param src The source array. It is not modified. + */ + public static void snapshot(@NonNull WatchedSparseIntArray dst, + @NonNull WatchedSparseIntArray src) { + if (dst.size() != 0) { + throw new IllegalArgumentException("snapshot destination is not empty"); + } + final int end = src.size(); + for (int i = 0; i < end; i++) { + dst.put(src.keyAt(i), src.valueAt(i)); + } + dst.seal(); + } + +} diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index c0608072df9d..d8a145d9ae33 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -36,6 +36,8 @@ import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; +import android.telephony.TelephonyManager; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -79,6 +81,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { @NonNull private final TelephonySubscriptionTrackerCallback mCallback; @NonNull private final Dependencies mDeps; + @NonNull private final TelephonyManager mTelephonyManager; @NonNull private final SubscriptionManager mSubscriptionManager; @NonNull private final CarrierConfigManager mCarrierConfigManager; @@ -106,6 +109,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { mCallback = Objects.requireNonNull(callback, "Missing callback"); mDeps = Objects.requireNonNull(deps, "Missing deps"); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class); @@ -139,7 +143,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { * so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking. */ public void handleSubscriptionsChanged() { - final Set<ParcelUuid> activeSubGroups = new ArraySet<>(); + final Map<ParcelUuid, Set<String>> privilegedPackages = new HashMap<>(); final Map<Integer, ParcelUuid> newSubIdToGroupMap = new HashMap<>(); final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList(); @@ -166,12 +170,22 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { // group. if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) { - activeSubGroups.add(subInfo.getGroupUuid()); + // TODO (b/172619301): Cache based on callbacks from CarrierPrivilegesTracker + + final TelephonyManager subIdSpecificTelephonyManager = + mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()); + + final ParcelUuid subGroup = subInfo.getGroupUuid(); + final Set<String> pkgs = + privilegedPackages.getOrDefault(subGroup, new ArraySet<>()); + pkgs.addAll(subIdSpecificTelephonyManager.getPackagesWithCarrierPrivileges()); + + privilegedPackages.put(subGroup, pkgs); } } final TelephonySubscriptionSnapshot newSnapshot = - new TelephonySubscriptionSnapshot(newSubIdToGroupMap, activeSubGroups); + new TelephonySubscriptionSnapshot(newSubIdToGroupMap, privilegedPackages); // If snapshot was meaningfully updated, fire the callback if (!newSnapshot.equals(mCurrentSnapshot)) { @@ -231,22 +245,40 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */ public static class TelephonySubscriptionSnapshot { private final Map<Integer, ParcelUuid> mSubIdToGroupMap; - private final Set<ParcelUuid> mActiveGroups; + private final Map<ParcelUuid, Set<String>> mPrivilegedPackages; + + public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT = + new TelephonySubscriptionSnapshot(Collections.emptyMap(), Collections.emptyMap()); @VisibleForTesting(visibility = Visibility.PRIVATE) TelephonySubscriptionSnapshot( @NonNull Map<Integer, ParcelUuid> subIdToGroupMap, - @NonNull Set<ParcelUuid> activeGroups) { - mSubIdToGroupMap = Collections.unmodifiableMap( - Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null")); - mActiveGroups = Collections.unmodifiableSet( - Objects.requireNonNull(activeGroups, "activeGroups was null")); + @NonNull Map<ParcelUuid, Set<String>> privilegedPackages) { + Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null"); + Objects.requireNonNull(privilegedPackages, "privilegedPackages was null"); + + mSubIdToGroupMap = Collections.unmodifiableMap(subIdToGroupMap); + + final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>(); + for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) { + unmodifiableInnerSets.put( + entry.getKey(), Collections.unmodifiableSet(entry.getValue())); + } + mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets); } /** Returns the active subscription groups */ @NonNull public Set<ParcelUuid> getActiveSubscriptionGroups() { - return mActiveGroups; + return mPrivilegedPackages.keySet(); + } + + /** Checks if the provided package is carrier privileged for the specified sub group. */ + public boolean packageHasPermissionsForSubscriptionGroup( + @NonNull ParcelUuid subGrp, @NonNull String packageName) { + final Set<String> privilegedPackages = mPrivilegedPackages.get(subGrp); + + return privilegedPackages != null && privilegedPackages.contains(packageName); } /** Returns the Subscription Group for a given subId. */ @@ -273,7 +305,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { @Override public int hashCode() { - return Objects.hash(mSubIdToGroupMap, mActiveGroups); + return Objects.hash(mSubIdToGroupMap, mPrivilegedPackages); } @Override @@ -285,7 +317,15 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj; return mSubIdToGroupMap.equals(other.mSubIdToGroupMap) - && mActiveGroups.equals(other.mActiveGroups); + && mPrivilegedPackages.equals(other.mPrivilegedPackages); + } + + @Override + public String toString() { + return "TelephonySubscriptionSnapshot{ " + + "mSubIdToGroupMap=" + mSubIdToGroupMap + + ", mPrivilegedPackages=" + mPrivilegedPackages + + " }"; } } diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index d51d16b1b4df..9d21b9241c0d 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -16,32 +16,69 @@ package com.android.server.vcn; + import android.annotation.NonNull; +import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.vcn.VcnConfig; +import android.net.vcn.VcnGatewayConnectionConfig; import android.os.Handler; import android.os.Message; import android.os.ParcelUuid; +import android.util.Slog; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; /** * Represents an single instance of a VCN. * - * <p>Each Vcn instance manages all tunnels for a given subscription group, including per-capability - * networks, network selection, and multi-homing. + * <p>Each Vcn instance manages all {@link VcnGatewayConnection}(s) for a given subscription group, + * including per-capability networks, network selection, and multi-homing. * * @hide */ public class Vcn extends Handler { private static final String TAG = Vcn.class.getSimpleName(); + private static final int MSG_EVENT_BASE = 0; + private static final int MSG_CMD_BASE = 100; + + /** + * A carrier app updated the configuration. + * + * <p>Triggers update of config, re-evaluating all active and underlying networks. + * + * @param obj VcnConfig + */ + private static final int MSG_EVENT_CONFIG_UPDATED = MSG_EVENT_BASE; + + /** + * A NetworkRequest was added or updated. + * + * <p>Triggers an evaluation of all active networks, bringing up a new one if necessary. + * + * @param obj NetworkRequest + */ + private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1; + + /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */ + private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE; + @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final Dependencies mDeps; + @NonNull private final VcnNetworkRequestListener mRequestListener; + + @NonNull + private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections = + new HashMap<>(); @NonNull private VcnConfig mConfig; + private boolean mIsRunning = true; + public Vcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @@ -58,31 +95,123 @@ public class Vcn extends Handler { mVcnContext = vcnContext; mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); mDeps = Objects.requireNonNull(deps, "Missing deps"); + mRequestListener = new VcnNetworkRequestListener(); mConfig = Objects.requireNonNull(config, "Missing config"); + + // Register to receive cached and future NetworkRequests + mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener); } /** Asynchronously updates the configuration and triggers a re-evaluation of Networks */ public void updateConfig(@NonNull VcnConfig config) { Objects.requireNonNull(config, "Missing config"); - // TODO: Proxy to handler, and make config there. + + sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config)); } - /** Asynchronously tears down this Vcn instance, along with all tunnels and Networks */ - public void teardown() { - // TODO: Proxy to handler, and teardown there. + /** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */ + public void teardownAsynchronously() { + sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN)); } - /** Notifies this Vcn instance of a new NetworkRequest */ - public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { - Objects.requireNonNull(request, "Missing request"); + private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener { + @Override + public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { + Objects.requireNonNull(request, "Missing request"); - // TODO: Proxy to handler, and handle there. + sendMessage(obtainMessage(MSG_EVENT_NETWORK_REQUESTED, score, providerId, request)); + } } @Override public void handleMessage(@NonNull Message msg) { - // TODO: Do something + if (!mIsRunning) { + return; + } + + switch (msg.what) { + case MSG_EVENT_CONFIG_UPDATED: + handleConfigUpdated((VcnConfig) msg.obj); + break; + case MSG_EVENT_NETWORK_REQUESTED: + handleNetworkRequested((NetworkRequest) msg.obj, msg.arg1, msg.arg2); + break; + case MSG_CMD_TEARDOWN: + handleTeardown(); + break; + default: + Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what); + } + } + + private void handleConfigUpdated(@NonNull VcnConfig config) { + // TODO: Add a dump function in VcnConfig that omits PII. Until then, use hashCode() + Slog.v(getLogTag(), String.format("Config updated: config = %s", config.hashCode())); + + mConfig = config; + + // TODO: Reevaluate active VcnGatewayConnection(s) + } + + private void handleTeardown() { + mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener); + + for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { + gatewayConnection.teardownAsynchronously(); + } + + mIsRunning = false; + } + + private void handleNetworkRequested( + @NonNull NetworkRequest request, int score, int providerId) { + if (score > getNetworkScore()) { + Slog.v(getLogTag(), + "Request " + request.requestId + " already satisfied by higher-scoring (" + + score + ") network from provider " + providerId); + return; + } + + // If preexisting VcnGatewayConnection(s) satisfy request, return + for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) { + if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { + Slog.v(getLogTag(), + "Request " + request.requestId + + " satisfied by existing VcnGatewayConnection"); + return; + } + } + + // If any supported (but not running) VcnGatewayConnection(s) can satisfy request, bring it + // up + for (VcnGatewayConnectionConfig gatewayConnectionConfig : + mConfig.getGatewayConnectionConfigs()) { + if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { + Slog.v( + getLogTag(), + "Bringing up new VcnGatewayConnection for request " + request.requestId); + + final VcnGatewayConnection vcnGatewayConnection = + new VcnGatewayConnection( + mVcnContext, mSubscriptionGroup, gatewayConnectionConfig); + mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection); + } + } + } + + private boolean requestSatisfiedByGatewayConnectionConfig( + @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) { + final NetworkCapabilities configCaps = new NetworkCapabilities(); + for (int cap : config.getAllExposedCapabilities()) { + configCaps.addCapability(cap); + } + + return request.networkCapabilities.satisfiedByNetworkCapabilities(configCaps); + } + + private String getLogTag() { + return String.format("%s [%d]", TAG, mSubscriptionGroup.hashCode()); } /** Retrieves the network score for a VCN Network */ diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java index 8ab52931cae3..dba59bdbee7d 100644 --- a/services/core/java/com/android/server/vcn/VcnContext.java +++ b/services/core/java/com/android/server/vcn/VcnContext.java @@ -20,8 +20,6 @@ import android.annotation.NonNull; import android.content.Context; import android.os.Looper; -import com.android.server.VcnManagementService.VcnNetworkProvider; - import java.util.Objects; /** diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 49c9b3297c77..7ea8e04580f7 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -65,8 +65,8 @@ public class VcnGatewayConnection extends Handler implements UnderlyingNetworkTr mDeps.newUnderlyingNetworkTracker(mVcnContext, subscriptionGroup, this); } - /** Tears down this GatewayConnection, and any resources used */ - public void teardown() { + /** Asynchronously tears down this GatewayConnection, and any resources used */ + public void teardownAsynchronously() { mUnderlyingNetworkTracker.teardown(); } diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java new file mode 100644 index 000000000000..7f5b23c9db6f --- /dev/null +++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java @@ -0,0 +1,108 @@ +/* + * 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.vcn; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.NetworkProvider; +import android.net.NetworkRequest; +import android.os.Looper; +import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; + +import java.util.Objects; +import java.util.Set; + +/** + * VCN Network Provider routes NetworkRequests to listeners to bring up tunnels as needed. + * + * <p>The VcnNetworkProvider provides a caching layer to ensure that all listeners receive all + * active NetworkRequest(s), including ones that were filed prior to listener registration. + * + * @hide + */ +public class VcnNetworkProvider extends NetworkProvider { + private static final String TAG = VcnNetworkProvider.class.getSimpleName(); + + private final Set<NetworkRequestListener> mListeners = new ArraySet<>(); + private final SparseArray<NetworkRequestEntry> mRequests = new SparseArray<>(); + + public VcnNetworkProvider(Context context, Looper looper) { + super(context, looper, VcnNetworkProvider.class.getSimpleName()); + } + + // Package-private + void registerListener(@NonNull NetworkRequestListener listener) { + mListeners.add(listener); + + // Send listener all cached requests + for (int i = 0; i < mRequests.size(); i++) { + notifyListenerForEvent(listener, mRequests.valueAt(i)); + } + } + + // Package-private + void unregisterListener(@NonNull NetworkRequestListener listener) { + mListeners.remove(listener); + } + + private void notifyListenerForEvent( + @NonNull NetworkRequestListener listener, @NonNull NetworkRequestEntry entry) { + listener.onNetworkRequested(entry.mRequest, entry.mScore, entry.mProviderId); + } + + @Override + public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { + Slog.v( + TAG, + String.format( + "Network requested: Request = %s, score = %d, providerId = %d", + request, score, providerId)); + + final NetworkRequestEntry entry = new NetworkRequestEntry(request, score, providerId); + mRequests.put(request.requestId, entry); + + // TODO(b/176939047): Intelligently route requests to prioritized VcnInstances (based on + // Default Data Sub, or similar) + for (NetworkRequestListener listener : mListeners) { + notifyListenerForEvent(listener, entry); + } + } + + @Override + public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) { + mRequests.remove(request.requestId); + } + + private static class NetworkRequestEntry { + public final NetworkRequest mRequest; + public final int mScore; + public final int mProviderId; + + private NetworkRequestEntry(@NonNull NetworkRequest request, int score, int providerId) { + mRequest = Objects.requireNonNull(request, "Missing request"); + mScore = score; + mProviderId = providerId; + } + } + + // package-private + interface NetworkRequestListener { + void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId); + } +} diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index b0847879f456..03cf021d0e9b 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 @@ class ActivityMetricsLogger { 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 @@ class ActivityMetricsLogger { 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 324e3acf8e73..a9c54741fcf6 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 class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 5289f8604f36..c6cc83b4c3bb 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 @@ class ActivityStarter { // 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 @@ class ActivityStarter { /** * 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; } @@ -1618,7 +1595,8 @@ class ActivityStarter { mService.getTransitionController().collectExistenceChange(r); } if (newTransition != null) { - mService.getTransitionController().requestStartTransition(newTransition); + mService.getTransitionController().requestStartTransition(newTransition, + r.getTask()); } else { // Make the collecting transition wait until this request is ready. mService.getTransitionController().setReady(false); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 9ffedde8e616..762e1f62f886 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -563,8 +563,13 @@ public abstract class ActivityTaskManagerInternal { */ 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 61da564ebab0..698013cc82ee 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -744,7 +744,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mActivityClientController.onSystemReady(); mBlockActivityAfterHomeEnabled = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - BLOCK_ACTIVITY_STARTS_AFTER_HOME_FLAG, false); + BLOCK_ACTIVITY_STARTS_AFTER_HOME_FLAG, true); } } @@ -3204,33 +3204,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } /** - * Moves the top activity in the input rootTaskId to the pinned root task. - * - * @param rootTaskId Id of root task to move the top activity to pinned root task. - * @param bounds Bounds to use for pinned root task. - * @return True if the top activity of the input stack was successfully moved to the pinned root - * task. - */ - @Override - public boolean moveTopActivityToPinnedRootTask(int rootTaskId, Rect bounds) { - enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, - "moveTopActivityToPinnedRootTask()"); - synchronized (mGlobalLock) { - if (!mSupportsPictureInPicture) { - throw new IllegalStateException("moveTopActivityToPinnedRootTask:" - + "Device doesn't support picture-in-picture mode"); - } - - final long ident = Binder.clearCallingIdentity(); - try { - return mRootWindowContainer.moveTopRootTaskActivityToPinnedRootTask(rootTaskId); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - - /** * Puts the given activity in picture in picture mode if possible. * * @return true if the activity is now in picture-in-picture mode, or false if it could not @@ -6299,17 +6272,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @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 9d291b12c2c9..599bf3748dd9 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { */ 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { /** 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { 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(); } @@ -2207,6 +2197,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } if (!task.supportsSplitScreenWindowingMode() || forceNonResizable) { + if (mService.getTransitionController().getTransitionPlayer() != null) return; // Dismiss docked stack. If task appeared to be in docked stack but is not resizable - // we need to move it to top of fullscreen stack, otherwise it will be covered. final TaskDisplayArea taskDisplayArea = task.getDisplayArea(); @@ -2603,32 +2594,30 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { /** * Internal container to store a match qualifier alongside a WaitResult. */ - static class WaitInfo { - private final ComponentName mTargetComponent; - private final WaitResult mResult; - - WaitInfo(ComponentName targetComponent, WaitResult result) { - this.mTargetComponent = targetComponent; - this.mResult = result; - } - - public boolean matches(ComponentName targetComponent) { - return mTargetComponent == null || mTargetComponent.equals(targetComponent); - } + 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; - public WaitResult getResult() { - return mResult; + WaitInfo(WaitResult result, ComponentName component, LaunchingState launchingState) { + mResult = result; + mTargetComponent = component; + mLaunchingState = launchingState; } - public ComponentName getComponent() { - return mTargetComponent; + boolean matches(ActivityRecord r) { + return mTargetComponent.equals(r.mActivityComponent) || mLaunchingState.contains(r); } - 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/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index dde527d161e0..d562bde45925 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -154,7 +154,7 @@ public class AppTransitionController { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO"); // TODO(new-app-transition): Remove code using appTransition.getAppTransition() final AppTransition appTransition = mDisplayContent.mAppTransition; - mDisplayContent.mSkipAppTransitionAnimation = false; + mDisplayContent.mNoAnimationNotifyOnTransitionFinished.clear(); appTransition.removeAppTransitionTimeoutCallbacks(); @@ -188,7 +188,9 @@ public class AppTransitionController { final @TransitionOldType int transit = getTransitCompatType( mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper()); + mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(), + mDisplayContent.mSkipAppTransitionAnimation); + mDisplayContent.mSkipAppTransitionAnimation = false; ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "handleAppTransitionReady: displayId=%d appTransition={%s}" @@ -274,7 +276,8 @@ public class AppTransitionController { */ static @TransitionOldType int getTransitCompatType(AppTransition appTransition, ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps, - @Nullable WindowState wallpaperTarget, @Nullable WindowState oldWallpaper) { + @Nullable WindowState wallpaperTarget, @Nullable WindowState oldWallpaper, + boolean skipAppTransitionAnimation) { // Determine if closing and opening app token sets are wallpaper targets, in which case // special animations are needed. @@ -298,6 +301,10 @@ public class AppTransitionController { return TRANSIT_OLD_KEYGUARD_UNOCCLUDE; } + // This is not keyguard transition and one of the app has request to skip app transition. + if (skipAppTransitionAnimation) { + return WindowManager.TRANSIT_OLD_UNSET; + } final @TransitionFlags int flags = appTransition.getTransitFlags(); final @TransitionType int firstTransit = appTransition.getFirstAppTransition(); diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java index 5952164d6554..eeb7fac30944 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java @@ -24,11 +24,13 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION; import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT; import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER; import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED; +import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL; import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION; import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature; @@ -131,14 +133,19 @@ public abstract class DisplayAreaPolicy { .all() .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR, TYPE_NOTIFICATION_SHADE) - .build()); + .build()) + .addFeature(new Feature.Builder(wmService.mPolicy, + "OneHandedBackgroundPanel", + FEATURE_ONE_HANDED_BACKGROUND_PANEL) + .upTo(TYPE_WALLPAPER) + .build()) + .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded", + FEATURE_ONE_HANDED) + .all() + .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL) + .build()); } rootHierarchy - .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded", - FEATURE_ONE_HANDED) - .all() - .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL) - .build()) .addFeature(new Feature.Builder(wmService.mPolicy, "FullscreenMagnification", FEATURE_FULLSCREEN_MAGNIFICATION) .all() diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 4c60a3d4a1a6..c3a754279f80 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -332,6 +332,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp DisplayCutout mInitialDisplayCutout; private final RotationCache<DisplayCutout, WmDisplayCutout> mDisplayCutoutCache = new RotationCache<>(this::calculateDisplayCutoutForRotationUncached); + boolean mIgnoreDisplayCutout; /** * Overridden display size. Initialized with {@link #mInitialDisplayWidth} @@ -2507,7 +2508,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final int newWidth = rotated ? mDisplayInfo.logicalHeight : mDisplayInfo.logicalWidth; final int newHeight = rotated ? mDisplayInfo.logicalWidth : mDisplayInfo.logicalHeight; final int newDensity = mDisplayInfo.logicalDensityDpi; - final DisplayCutout newCutout = mDisplayInfo.displayCutout; + final DisplayCutout newCutout = mIgnoreDisplayCutout + ? DisplayCutout.NO_CUTOUT : mDisplayInfo.displayCutout; final String newUniqueId = mDisplayInfo.uniqueId; final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index 5d4dbc8388c4..b82fdd237f2b 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java @@ -261,6 +261,10 @@ class DisplayWindowSettings { ? settings.mIgnoreOrientationRequest : false; dc.setIgnoreOrientationRequest(ignoreOrientationRequest); + final boolean ignoreDisplayCutout = settings.mIgnoreDisplayCutout != null + ? settings.mIgnoreDisplayCutout : false; + dc.mIgnoreDisplayCutout = ignoreDisplayCutout; + final int width = hasSizeOverride ? settings.mForcedWidth : dc.mInitialDisplayWidth; final int height = hasSizeOverride ? settings.mForcedHeight : dc.mInitialDisplayHeight; final int density = hasDensityOverride ? settings.mForcedDensity @@ -352,6 +356,8 @@ class DisplayWindowSettings { Integer mFixedToUserRotation; @Nullable Boolean mIgnoreOrientationRequest; + @Nullable + Boolean mIgnoreDisplayCutout; SettingsEntry() {} @@ -422,6 +428,10 @@ class DisplayWindowSettings { mIgnoreOrientationRequest = other.mIgnoreOrientationRequest; changed = true; } + if (other.mIgnoreDisplayCutout != mIgnoreDisplayCutout) { + mIgnoreDisplayCutout = other.mIgnoreDisplayCutout; + changed = true; + } return changed; } @@ -500,6 +510,11 @@ class DisplayWindowSettings { mIgnoreOrientationRequest = delta.mIgnoreOrientationRequest; changed = true; } + if (delta.mIgnoreDisplayCutout != null + && delta.mIgnoreDisplayCutout != mIgnoreDisplayCutout) { + mIgnoreDisplayCutout = delta.mIgnoreDisplayCutout; + changed = true; + } return changed; } @@ -515,7 +530,8 @@ class DisplayWindowSettings { && mShouldShowSystemDecors == null && mImePolicy == null && mFixedToUserRotation == null - && mIgnoreOrientationRequest == null; + && mIgnoreOrientationRequest == null + && mIgnoreDisplayCutout == null; } @Override @@ -536,8 +552,8 @@ class DisplayWindowSettings { && Objects.equals(mShouldShowSystemDecors, that.mShouldShowSystemDecors) && Objects.equals(mImePolicy, that.mImePolicy) && Objects.equals(mFixedToUserRotation, that.mFixedToUserRotation) - && Objects.equals(mIgnoreOrientationRequest, - that.mIgnoreOrientationRequest); + && Objects.equals(mIgnoreOrientationRequest, that.mIgnoreOrientationRequest) + && Objects.equals(mIgnoreDisplayCutout, that.mIgnoreDisplayCutout); } @Override @@ -545,7 +561,7 @@ class DisplayWindowSettings { return Objects.hash(mWindowingMode, mUserRotationMode, mUserRotation, mForcedWidth, mForcedHeight, mForcedDensity, mForcedScalingMode, mRemoveContentMode, mShouldShowWithInsecureKeyguard, mShouldShowSystemDecors, mImePolicy, - mFixedToUserRotation, mIgnoreOrientationRequest); + mFixedToUserRotation, mIgnoreOrientationRequest, mIgnoreDisplayCutout); } @Override @@ -564,6 +580,7 @@ class DisplayWindowSettings { + ", mShouldShowIme=" + mImePolicy + ", mFixedToUserRotation=" + mFixedToUserRotation + ", mIgnoreOrientationRequest=" + mIgnoreOrientationRequest + + ", mIgnoreDisplayCutout=" + mIgnoreDisplayCutout + '}'; } } diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java index 57c947f572d9..5f3ab43eca7c 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java @@ -403,6 +403,8 @@ class DisplayWindowSettingsProvider implements SettingsProvider { null /* defaultValue */); settingsEntry.mIgnoreOrientationRequest = getBooleanAttribute(parser, "ignoreOrientationRequest", null /* defaultValue */); + settingsEntry.mIgnoreDisplayCutout = getBooleanAttribute(parser, + "ignoreDisplayCutout", null /* defaultValue */); fileData.mSettings.put(name, settingsEntry); } XmlUtils.skipCurrentTag(parser); @@ -490,6 +492,10 @@ class DisplayWindowSettingsProvider implements SettingsProvider { out.attributeBoolean(null, "ignoreOrientationRequest", settingsEntry.mIgnoreOrientationRequest); } + if (settingsEntry.mIgnoreDisplayCutout != null) { + out.attributeBoolean(null, "ignoreDisplayCutout", + settingsEntry.mIgnoreDisplayCutout); + } out.endTag(null, "display"); } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index ff5b356d16ad..40e7a8e9311b 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.os.Build.IS_DEBUGGABLE; import static android.view.InsetsState.ITYPE_CLIMATE_BAR; import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_IME; @@ -552,6 +553,11 @@ class InsetsSourceProvider { // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed. t.setAlpha(animationLeash, 1 /* alpha */); t.hide(animationLeash); + + // TODO(b/175954493): Remove this after finding root cause. + if (IS_DEBUGGABLE) { + animationLeash.setDebugRelease(true); + } } ProtoLog.i(WM_DEBUG_IME, "ControlAdapter startAnimation mSource: %s controlTarget: %s", mSource, diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index dc75bbeea452..6e8110e9c36e 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1496,6 +1496,7 @@ class RecentTasks { final Task removedTask = mTasks.remove(removeIndex); if (removedTask != task) { if (removedTask.hasChild()) { + Slog.i(TAG, "Add " + removedTask + " to hidden list because adding " + task); // A non-empty task is replaced by a new task. Because the removed task is no longer // managed by the recent tasks list, add it to the hidden list to prevent the task // from becoming dangling. diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a0e6b423f299..a88e4537d1a2 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4421,8 +4421,17 @@ class Task extends WindowContainer<WindowContainer> { sb.append(stringName); sb.append(" U="); sb.append(mUserId); - sb.append(" StackId="); - sb.append(getRootTaskId()); + final Task rootTask = getRootTask(); + if (rootTask != this) { + sb.append(" rootTaskId="); + sb.append(rootTask.mTaskId); + } + sb.append(" visible="); + sb.append(shouldBeVisible(null /* starting */)); + sb.append(" mode="); + sb.append(windowingModeToString(getWindowingMode())); + sb.append(" translucent="); + sb.append(isTranslucent(null /* starting */)); sb.append(" sz="); sb.append(getChildCount()); sb.append('}'); @@ -4432,10 +4441,7 @@ class Task extends WindowContainer<WindowContainer> { sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" #"); sb.append(mTaskId); - sb.append(" visible=" + shouldBeVisible(null /* starting */)); sb.append(" type=" + activityTypeToString(getActivityType())); - sb.append(" mode=" + windowingModeToString(getWindowingMode())); - sb.append(" translucent=" + isTranslucent(null /* starting */)); if (affinity != null) { sb.append(" A="); sb.append(affinity); @@ -5098,7 +5104,9 @@ class Task extends WindowContainer<WindowContainer> { } void onPictureInPictureParamsChanged() { - dispatchTaskInfoChangedIfNeeded(true /* force */); + if (inPinnedWindowingMode()) { + dispatchTaskInfoChangedIfNeeded(true /* force */); + } } /** @@ -5180,9 +5188,6 @@ class Task extends WindowContainer<WindowContainer> { @Override public void setWindowingMode(int windowingMode) { - // Reset the cached result of toString() - stringName = null; - // Calling Task#setWindowingMode() for leaf task since this is the a specialization of // {@link #setWindowingMode(int)} for ActivityStack. if (!isRootTask()) { @@ -5433,14 +5438,6 @@ class Task extends WindowContainer<WindowContainer> { 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(), @@ -5583,7 +5580,6 @@ class Task extends WindowContainer<WindowContainer> { mLastNoHistoryActivity = prev.isNoHistory() ? prev : null; prev.setState(PAUSING, "startPausingLocked"); prev.getTask().touchActiveTime(); - clearLaunchTime(prev); mAtmService.updateCpuStats(); @@ -6099,13 +6095,13 @@ class Task extends WindowContainer<WindowContainer> { mTaskSupervisor.setLaunchSource(next.info.applicationInfo.uid); ActivityRecord lastResumed = null; - final Task lastFocusedStack = taskDisplayArea.getLastFocusedRootTask(); - if (lastFocusedStack != null && lastFocusedStack != this) { + final Task lastFocusedRootTask = taskDisplayArea.getLastFocusedRootTask(); + if (lastFocusedRootTask != null && lastFocusedRootTask != getRootTask()) { // So, why aren't we using prev here??? See the param comment on the method. prev // doesn't represent the last resumed activity. However, the last focus stack does if // it isn't null. - lastResumed = lastFocusedStack.getResumedActivity(); - if (userLeaving && inMultiWindowMode() && lastFocusedStack.shouldBeVisible(next)) { + lastResumed = lastFocusedRootTask.getResumedActivity(); + if (userLeaving && inMultiWindowMode() && lastFocusedRootTask.shouldBeVisible(next)) { // The user isn't leaving if this stack is the multi-window mode and the last // focused stack should still be visible. if(DEBUG_USER_LEAVING) Slog.i(TAG_USER_LEAVING, "Overriding userLeaving to false" @@ -6259,10 +6255,10 @@ class Task extends WindowContainer<WindowContainer> { // Launcher is already visible in this case. If we don't add it to opening // apps, maybeUpdateTransitToWallpaper() will fail to identify this as a // TRANSIT_WALLPAPER_OPEN animation, and run some funny animation. - final boolean lastActivityTranslucent = lastFocusedStack != null - && (lastFocusedStack.inMultiWindowMode() - || (lastFocusedStack.mLastPausedActivity != null - && !lastFocusedStack.mLastPausedActivity.occludesParent())); + final boolean lastActivityTranslucent = lastFocusedRootTask != null + && (lastFocusedRootTask.inMultiWindowMode() + || (lastFocusedRootTask.mLastPausedActivity != null + && !lastFocusedRootTask.mLastPausedActivity.occludesParent())); // This activity is now becoming visible. if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) { @@ -6273,7 +6269,7 @@ class Task extends WindowContainer<WindowContainer> { next.startLaunchTickingLocked(); ActivityRecord lastResumedActivity = - lastFocusedStack == null ? null : lastFocusedStack.getResumedActivity(); + lastFocusedRootTask == null ? null : lastFocusedRootTask.getResumedActivity(); final ActivityState lastState = next.getState(); mAtmService.updateCpuStats(); @@ -6370,8 +6366,8 @@ class Task extends WindowContainer<WindowContainer> { Slog.i(TAG, "Restarting because process died: " + next); if (!next.hasBeenLaunched) { next.hasBeenLaunched = true; - } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedStack != null - && lastFocusedStack.isTopRootTaskInDisplayArea()) { + } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null + && lastFocusedRootTask.isTopRootTaskInDisplayArea()) { next.showStartingWindow(null /* prev */, false /* newTask */, false /* taskSwitch */); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index be78d7a18377..b37e3c42f568 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -37,6 +37,7 @@ import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import android.annotation.IntDef; import android.annotation.NonNull; +import android.app.ActivityManager; import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; @@ -567,6 +568,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe tmpList.add(wc); } for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) { + if (!p.isAttached() || !changes.get(p).hasChanged(p)) { + // Again, we're skipping no-ops + break; + } if (participants.contains(p)) { topParent = p; break; @@ -695,6 +700,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final TransitionInfo.Change change = new TransitionInfo.Change( target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken() : null, target.getSurfaceControl()); + // TODO(shell-transitions): Use leash for non-organized windows. if (info.mParent != null) { change.setParent(info.mParent.mRemoteToken.toWindowContainerToken()); } @@ -704,6 +710,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe change.setEndRelOffset(target.getBounds().left - target.getParent().getBounds().left, target.getBounds().top - target.getParent().getBounds().top); change.setFlags(info.getChangeFlags(target)); + final Task task = target.asTask(); + if (task != null) { + final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo(); + task.fillTaskInfo(tinfo); + change.setTaskInfo(tinfo); + } out.addChange(change); } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 2f5d10afe3da..0fe0afa54dd3 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; @@ -167,7 +168,8 @@ class TransitionController { // Make the collecting transition wait until this request is ready. mCollectingTransition.setReady(false); } else { - newTransition = requestStartTransition(createTransition(type, flags)); + newTransition = requestStartTransition(createTransition(type, flags), + trigger != null ? trigger.asTask() : null); } if (trigger != null) { if (isExistenceType(type)) { @@ -181,11 +183,16 @@ class TransitionController { /** Asks the transition player (shell) to start a created but not yet started transition. */ @NonNull - Transition requestStartTransition(@NonNull Transition transition) { + Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask) { try { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Requesting StartTransition: %s", transition); - mTransitionPlayer.requestStartTransition(transition.mType, transition); + ActivityManager.RunningTaskInfo info = null; + if (startTask != null) { + info = new ActivityManager.RunningTaskInfo(); + startTask.fillTaskInfo(info); + } + mTransitionPlayer.requestStartTransition(transition.mType, transition, info); } catch (RemoteException e) { Slog.e(TAG, "Error requesting transition", e); transition.start(); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 5f2b628e056a..b0e67ce17711 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -19,8 +19,8 @@ package com.android.server.wm; import static android.Manifest.permission.READ_FRAME_BUFFER; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; -import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED; +import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -163,6 +163,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub try { synchronized (mGlobalLock) { Transition transition = Transition.fromBinder(transitionToken); + // In cases where transition is already provided, the "readiness lifecycle" of the + // transition is determined outside of this transaction. However, if this is a + // direct call from shell, the entire transition lifecycle is contained in the + // provided transaction and thus we can setReady immediately after apply. + boolean needsSetReady = transition == null && t != null; if (transition == null) { if (type < 0) { throw new IllegalArgumentException("Can't create transition with no type"); @@ -174,6 +179,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub t = new WindowContainerTransaction(); } applyTransaction(t, -1 /*syncId*/, transition); + if (needsSetReady) { + transition.setReady(); + } return transition; } } finally { @@ -258,14 +266,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } if (transition != null) { transition.collect(wc); - if (hop.isReparent() && 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 (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); @@ -307,6 +322,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { // Already calls ensureActivityConfig mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); + mService.mRootWindowContainer.resumeFocusedTasksTopActivities(); } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { final PooledConsumer f = PooledLambda.obtainConsumer( ActivityRecord::ensureActivityConfiguration, diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 996462f22e8f..13078b68c92b 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -171,7 +171,6 @@ cc_defaults { static_libs: [ "android.hardware.broadcastradio@common-utils-1x-lib", - "libservice-connectivity-static", ], product_variables: { diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index e3a8bb4e405e..35aad3e268ed 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -31,6 +31,7 @@ #include <android/hardware/gnss/2.1/IGnssMeasurement.h> #include <android/hardware/gnss/3.0/IGnssPsds.h> #include <android/hardware/gnss/BnGnss.h> +#include <android/hardware/gnss/BnGnssCallback.h> #include <android/hardware/gnss/BnGnssMeasurementCallback.h> #include <android/hardware/gnss/BnGnssPowerIndicationCallback.h> #include <android/hardware/gnss/BnGnssPsdsCallback.h> @@ -223,6 +224,7 @@ using android::hardware::gnss::IGnssPowerIndication; using android::hardware::gnss::IGnssPowerIndicationCallback; using android::hardware::gnss::PsdsType; using IGnssAidl = android::hardware::gnss::IGnss; +using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback; using IGnssPsdsAidl = android::hardware::gnss::IGnssPsds; using IGnssPsdsCallbackAidl = android::hardware::gnss::IGnssPsdsCallback; using IGnssConfigurationAidl = android::hardware::gnss::IGnssConfiguration; @@ -711,6 +713,19 @@ Return<void> GnssCallback::gnssSetSystemInfoCb(const IGnssCallback_V2_0::GnssSys return Void(); } +class GnssCallbackAidl : public android::hardware::gnss::BnGnssCallback { +public: + Status gnssSetCapabilitiesCb(const int capabilities) override; +}; + +Status GnssCallbackAidl::gnssSetCapabilitiesCb(const int capabilities) { + ALOGD("GnssCallbackAidl::%s: %du\n", __func__, capabilities); + JNIEnv* env = getJniEnv(); + env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return Status::ok(); +} + /* * GnssPowerIndicationCallback class implements the callback methods for the IGnssPowerIndication * interface. @@ -1987,6 +2002,14 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl return JNI_FALSE; } + sp<IGnssCallbackAidl> gnssCbIfaceAidl = new GnssCallbackAidl(); + if (gnssHalAidl != nullptr) { + auto status = gnssHalAidl->setCallback(gnssCbIfaceAidl); + if (!checkAidlStatus(status, "IGnssAidl setCallback() failed.")) { + return JNI_FALSE; + } + } + // Set IGnssPsds or IGnssXtra callback. if (gnssPsdsAidlIface != nullptr) { sp<IGnssPsdsCallbackAidl> gnssPsdsCallbackAidl = new GnssPsdsCallbackAidl(); diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 85ef39470cb3..189332107a5d 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -41,8 +41,6 @@ int register_android_server_vr_VrManagerService(JNIEnv* env); 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 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) 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/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 88964b746aca..1cb9e5786a70 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -33,6 +33,7 @@ <xs:annotation name="nonnull"/> <xs:annotation name="final"/> </xs:element> + <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0" maxOccurs="1"/> <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" /> </xs:sequence> </xs:complexType> @@ -46,6 +47,38 @@ </xs:sequence> </xs:complexType> + <xs:complexType name="highBrightnessMode"> + <xs:all> + <xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1" maxOccurs="1"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <xs:element name="minimumLux" type="nonNegativeDecimal" minOccurs="1" maxOccurs="1"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <xs:element name="timing" type="hbmTiming" minOccurs="1" maxOccurs="1"/> + </xs:all> + <xs:attribute name="enabled" type="xs:boolean" use="optional"/> + </xs:complexType> + + <xs:complexType name="hbmTiming"> + <xs:all> + <xs:element name="timeWindowSecs" type="xs:nonNegativeInteger" minOccurs="1" maxOccurs="1"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <xs:element name="timeMaxSecs" type="xs:nonNegativeInteger" minOccurs="1" maxOccurs="1"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <xs:element name="timeMinSecs" type="xs:nonNegativeInteger" minOccurs="1" maxOccurs="1"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + </xs:all> + </xs:complexType> + <xs:complexType name="nitsMap"> <xs:sequence> <xs:element name="point" type="point" maxOccurs="unbounded" minOccurs="2"> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 6906fda5b76f..e073ab3675ca 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -3,9 +3,11 @@ package com.android.server.display.config { public class DisplayConfiguration { ctor public DisplayConfiguration(); + method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); method public com.android.server.display.config.DisplayQuirks getQuirks(); method @NonNull public final java.math.BigDecimal getScreenBrightnessDefault(); method @NonNull public final com.android.server.display.config.NitsMap getScreenBrightnessMap(); + method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); method public void setQuirks(com.android.server.display.config.DisplayQuirks); method public final void setScreenBrightnessDefault(@NonNull java.math.BigDecimal); method public final void setScreenBrightnessMap(@NonNull com.android.server.display.config.NitsMap); @@ -16,6 +18,28 @@ package com.android.server.display.config { method public java.util.List<java.lang.String> getQuirk(); } + public class HbmTiming { + ctor public HbmTiming(); + method @NonNull public final java.math.BigInteger getTimeMaxSecs_all(); + method @NonNull public final java.math.BigInteger getTimeMinSecs_all(); + method @NonNull public final java.math.BigInteger getTimeWindowSecs_all(); + method public final void setTimeMaxSecs_all(@NonNull java.math.BigInteger); + method public final void setTimeMinSecs_all(@NonNull java.math.BigInteger); + method public final void setTimeWindowSecs_all(@NonNull java.math.BigInteger); + } + + public class HighBrightnessMode { + ctor public HighBrightnessMode(); + method public boolean getEnabled(); + method @NonNull public final java.math.BigDecimal getMinimumLux_all(); + method public com.android.server.display.config.HbmTiming getTiming_all(); + method @NonNull public final java.math.BigDecimal getTransitionPoint_all(); + method public void setEnabled(boolean); + method public final void setMinimumLux_all(@NonNull java.math.BigDecimal); + method public void setTiming_all(com.android.server.display.config.HbmTiming); + method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal); + } + public class NitsMap { ctor public NitsMap(); method @NonNull public final java.util.List<com.android.server.display.config.Point> getPoint(); diff --git a/services/core/xsd/platform-compat-config.xsd b/services/core/xsd/platform-compat-config.xsd index 992470816068..a62e2c385766 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 e3640edd0201..fb8bbefd8374 100644 --- a/services/core/xsd/platform-compat-schema/current.txt +++ b/services/core/xsd/platform-compat-schema/current.txt @@ -10,6 +10,7 @@ package com.android.server.compat.config { 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 @@ package com.android.server.compat.config { 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 a281180d77d1..48f8b1505d3a 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 @@ class ActiveAdmin { 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 @@ class ActiveAdmin { 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 @@ class ActiveAdmin { 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 @@ class ActiveAdmin { 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 @@ class ActiveAdmin { 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 6f1d451e7224..22e9725f49ab 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; @@ -101,4 +102,11 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { 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/CallerIdentity.java b/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java index b6b4d8a04cb6..4e422b2374b2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java @@ -25,7 +25,7 @@ import android.os.UserHandle; * All parameters are verified on object creation unless the component name is null and the * caller is a delegate. */ -class CallerIdentity { +final class CallerIdentity { private final int mUid; @Nullable @@ -51,7 +51,7 @@ class CallerIdentity { return UserHandle.getUserHandleForUid(mUid); } - @Nullable public String getPackageName() { + @Nullable public String getPackageName() { return mPackageName; } @@ -66,4 +66,16 @@ class CallerIdentity { public boolean hasPackage() { return mPackageName != null; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("CallerIdentity[uid=").append(mUid); + if (mPackageName != null) { + builder.append(", pkg=").append(mPackageName); + } + if (mComponentName != null) { + builder.append(", cmp=").append(mComponentName.flattenToShortString()); + } + return builder.append("]").toString(); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index cd89f9ec12c2..c7d6d5dca973 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -819,6 +819,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } 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) @@ -949,6 +950,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + 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 @@ -983,8 +998,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) { - Slog.i(LOG_TAG, "Setting DevicePolicySafetyChecker as " + safetyChecker); + CallerIdentity callerIdentity = getCallerIdentity(); + Preconditions.checkCallAuthorization(mIsAutomotive || isAdb(callerIdentity), "can only set " + + "DevicePolicySafetyChecker on automotive builds or from ADB (but caller is %s)", + callerIdentity); + setDevicePolicySafetyCheckerUnchecked(safetyChecker); + } + + /** + * Used by {@code setDevicePolicySafetyChecker()} above and {@link OneTimeSafetyChecker}. + */ + void setDevicePolicySafetyCheckerUnchecked(DevicePolicySafetyChecker safetyChecker) { + Slog.i(LOG_TAG, String.format("Setting DevicePolicySafetyChecker as %s", safetyChecker)); mSafetyChecker = safetyChecker; + mInjector.setDevicePolicySafetyChecker(safetyChecker); } /** @@ -1021,8 +1048,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setNextOperationSafety(@DevicePolicyOperation int operation, boolean safe) { Preconditions.checkCallAuthorization( hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)); - Slog.i(LOG_TAG, "setNextOperationSafety(" + DevicePolicyManager.operationToString(operation) - + ", " + safe + ")"); + Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %b)", + DevicePolicyManager.operationToString(operation), safe)); mSafetyChecker = new OneTimeSafetyChecker(this, operation, safe); } @@ -1034,6 +1061,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public final Context mContext; + private @Nullable DevicePolicySafetyChecker mSafetyChecker; + Injector(Context context) { mContext = context; } @@ -1278,7 +1307,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force, boolean wipeEuicc, boolean wipeExtRequested, boolean wipeResetProtectionData) throws IOException { - FactoryResetter.newBuilder(mContext).setReason(reason).setShutdown(shutdown) + FactoryResetter.newBuilder(mContext).setSafetyChecker(mSafetyChecker) + .setReason(reason).setShutdown(shutdown) .setForce(force).setWipeEuicc(wipeEuicc) .setWipeAdoptableStorage(wipeExtRequested) .setWipeFactoryResetProtection(wipeResetProtectionData) @@ -1404,6 +1434,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return SecurityLog.isLoggingEnabled(); } + KeyChainConnection keyChainBind() throws InterruptedException { + return KeyChain.bind(mContext); + } + KeyChainConnection keyChainBindAsUser(UserHandle user) throws InterruptedException { return KeyChain.bindAsUser(mContext, user); } @@ -1433,6 +1467,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean isChangeEnabled(long changeId, String packageName, int userId) { return CompatChanges.isChangeEnabled(changeId, packageName, UserHandle.of(userId)); } + + void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) { + mSafetyChecker = safetyChecker; + } } /** @@ -3394,6 +3432,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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) { @@ -4359,6 +4410,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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. @@ -8402,7 +8467,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())); + return getProfileOwnerNameUnchecked(userHandle); + } + private String getProfileOwnerNameUnchecked(int userHandle) { ComponentName profileOwner = getProfileOwnerAsUser(userHandle); if (profileOwner == null) { return null; @@ -9712,7 +9780,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Set admin. setActiveAdmin(profileOwner, /* refreshing= */ true, userId); - final String ownerName = getProfileOwnerName(Process.myUserHandle().getIdentifier()); + final String ownerName = getProfileOwnerNameUnchecked( + Process.myUserHandle().getIdentifier()); setProfileOwner(profileOwner, ownerName, userId); synchronized (getLockObject()) { @@ -15558,4 +15627,69 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 000000000000..df7f3084aeb3 --- /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/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java b/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java index a0cf7a31650d..564950bef4f5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java @@ -17,14 +17,18 @@ package com.android.server.devicepolicy; import android.annotation.Nullable; +import android.app.admin.DevicePolicySafetyChecker; import android.content.Context; import android.content.pm.PackageManager; +import android.os.Bundle; import android.os.RecoverySystem; +import android.os.RemoteException; import android.os.UserManager; import android.os.storage.StorageManager; import android.service.persistentdata.PersistentDataBlockManager; -import android.util.Log; +import android.util.Slog; +import com.android.internal.os.IResultReceiver; import com.android.internal.util.Preconditions; import java.io.IOException; @@ -38,6 +42,7 @@ public final class FactoryResetter { private static final String TAG = FactoryResetter.class.getSimpleName(); private final Context mContext; + private final @Nullable DevicePolicySafetyChecker mSafetyChecker; private final @Nullable String mReason; private final boolean mShutdown; private final boolean mForce; @@ -45,18 +50,40 @@ public final class FactoryResetter { private final boolean mWipeAdoptableStorage; private final boolean mWipeFactoryResetProtection; - /** * Factory reset the device according to the builder's arguments. */ public void factoryReset() throws IOException { - Log.i(TAG, String.format("factoryReset(): reason=%s, shutdown=%b, force=%b, wipeEuicc=%b" - + ", wipeAdoptableStorage=%b, wipeFRP=%b", mReason, mShutdown, mForce, mWipeEuicc, - mWipeAdoptableStorage, mWipeFactoryResetProtection)); - Preconditions.checkCallAuthorization(mContext.checkCallingOrSelfPermission( android.Manifest.permission.MASTER_CLEAR) == PackageManager.PERMISSION_GRANTED); + if (mSafetyChecker == null) { + factoryResetInternalUnchecked(); + return; + } + + IResultReceiver receiver = new IResultReceiver.Stub() { + @Override + public void send(int resultCode, Bundle resultData) throws RemoteException { + Slog.i(TAG, String.format("Factory reset confirmed by %s, proceeding", + mSafetyChecker)); + try { + factoryResetInternalUnchecked(); + } catch (IOException e) { + // Shouldn't happen + Slog.wtf(TAG, "IOException calling underlying systems", e); + } + } + }; + Slog.i(TAG, String.format("Delaying factory reset until %s confirms", mSafetyChecker)); + mSafetyChecker.onFactoryReset(receiver); + } + + private void factoryResetInternalUnchecked() throws IOException { + Slog.i(TAG, String.format("factoryReset(): reason=%s, shutdown=%b, force=%b, wipeEuicc=%b, " + + "wipeAdoptableStorage=%b, wipeFRP=%b", mReason, mShutdown, mForce, mWipeEuicc, + mWipeAdoptableStorage, mWipeFactoryResetProtection)); + UserManager um = mContext.getSystemService(UserManager.class); if (!mForce && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) { throw new SecurityException("Factory reset is not allowed for this user."); @@ -66,15 +93,15 @@ public final class FactoryResetter { PersistentDataBlockManager manager = mContext .getSystemService(PersistentDataBlockManager.class); if (manager != null) { - Log.w(TAG, "Wiping factory reset protection"); + Slog.w(TAG, "Wiping factory reset protection"); manager.wipe(); } else { - Log.w(TAG, "No need to wipe factory reset protection"); + Slog.w(TAG, "No need to wipe factory reset protection"); } } if (mWipeAdoptableStorage) { - Log.w(TAG, "Wiping adoptable storage"); + Slog.w(TAG, "Wiping adoptable storage"); StorageManager sm = mContext.getSystemService(StorageManager.class); sm.wipeAdoptableDisks(); } @@ -84,8 +111,9 @@ public final class FactoryResetter { private FactoryResetter(Builder builder) { mContext = builder.mContext; - mShutdown = builder.mShutdown; + mSafetyChecker = builder.mSafetyChecker; mReason = builder.mReason; + mShutdown = builder.mShutdown; mForce = builder.mForce; mWipeEuicc = builder.mWipeEuicc; mWipeAdoptableStorage = builder.mWipeAdoptableStorage; @@ -105,6 +133,7 @@ public final class FactoryResetter { public static final class Builder { private final Context mContext; + private @Nullable DevicePolicySafetyChecker mSafetyChecker; private @Nullable String mReason; private boolean mShutdown; private boolean mForce; @@ -117,6 +146,15 @@ public final class FactoryResetter { } /** + * Sets a {@link DevicePolicySafetyChecker} object that will be used to delay the + * factory reset when it's not safe to do so. + */ + public Builder setSafetyChecker(@Nullable DevicePolicySafetyChecker safetyChecker) { + mSafetyChecker = safetyChecker; + return this; + } + + /** * Sets the (non-null) reason for the factory reset that is visible in the logs */ public Builder setReason(String reason) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java index b0f8bfb713ab..f7a826156d68 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java @@ -21,6 +21,8 @@ import android.app.admin.DevicePolicyManager.DevicePolicyOperation; import android.app.admin.DevicePolicySafetyChecker; import android.util.Slog; +import com.android.internal.os.IResultReceiver; + import java.util.Objects; //TODO(b/172376923): add unit tests @@ -61,7 +63,12 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { } Slog.i(TAG, "isDevicePolicyOperationSafe(" + name + "): returning " + safe + " and restoring DevicePolicySafetyChecker to " + mRealSafetyChecker); - mService.setDevicePolicySafetyChecker(mRealSafetyChecker); + mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker); return safe; } + + @Override + public void onFactoryReset(IResultReceiver callback) { + throw new UnsupportedOperationException(); + } } diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index a31aac96eb48..d2244286450b 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -122,13 +122,14 @@ binder::Status BinderIncrementalService::createStorage( 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 @@ binder::Status BinderIncrementalService::deleteStorage(int32_t storageId) { 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::isFileFullyLoaded(int32_t storageId, 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 8afa0f7bb117..9a4537a15f31 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -45,6 +45,7 @@ public: 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 @@ public: 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 eb6b325050eb..45c9ad9b344b 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -63,6 +63,10 @@ struct Constants { 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 +354,8 @@ void IncrementalService::onDump(int fd) { 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 +424,11 @@ auto IncrementalService::getStorageSlotLocked() -> MountMap::iterator { } } -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 +557,14 @@ StorageId IncrementalService::createStorage(std::string_view mountPoint, 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 +572,11 @@ StorageId IncrementalService::createStorage(std::string_view mountPoint, 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 +644,17 @@ StorageId IncrementalService::findStorageId(std::string_view path) const { 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,7 +679,7 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog 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; } @@ -704,7 +714,12 @@ binder::Status IncrementalService::applyStorageParams(IncFsMount& ifs, bool enab } 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 +1067,74 @@ bool IncrementalService::startLoading(StorageId storage) const { 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 +1208,7 @@ std::unordered_set<std::string_view> IncrementalService::adoptMountedInstances() // Check if marker file present. if (checkReadLogsDisabledMarker(root)) { - ifs->disableReadLogs(); + ifs->disallowReadLogs(); } std::vector<std::pair<std::string, metadata::BindPoint>> permanentBindPoints; @@ -1301,7 +1384,7 @@ bool IncrementalService::mountExistingImage(std::string_view root) { // Check if marker file present. if (checkReadLogsDisabledMarker(mountTarget)) { - ifs->disableReadLogs(); + ifs->disallowReadLogs(); } // DataLoader params @@ -1705,7 +1788,7 @@ int IncrementalService::setFileContent(const IfsMountPtr& ifs, const incfs::File 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 +1801,7 @@ int IncrementalService::isFileFullyLoaded(StorageId storage, const std::string& return -EINVAL; } l.unlock(); - return isFileFullyLoadedFromPath(*ifs, path); + return isFileFullyLoadedFromPath(*ifs, filePath); } int IncrementalService::isFileFullyLoadedFromPath(const IncFsMount& ifs, @@ -1736,25 +1819,26 @@ int IncrementalService::isFileFullyLoadedFromPath(const IncFsMount& ifs, 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 +1846,29 @@ float IncrementalService::getLoadingProgressFromPath(const IncFsMount& ifs, 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 eb69470c97a7..306612159412 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 StorageHealthListener = ::android::sp<IStorageHealthListener>; 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 @@ public: }; 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ private: 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 @@ private: 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 @@ private: 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 @@ private: 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/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp index dfe9684779fe..b1521b0d4e27 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -206,6 +206,11 @@ public: 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 f2d00735bc44..fad8d67e0da7 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 @@ public: 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 9b8cf4084bf1..02eaa2fe07b1 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -42,6 +42,7 @@ using testing::NiceMock; using namespace android::incfs; using namespace android::content::pm; +using PerUidReadTimeouts = android::os::incremental::PerUidReadTimeouts; namespace android::os::incremental { @@ -307,6 +308,9 @@ public: 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()); } @@ -665,7 +669,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -676,7 +680,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -689,7 +693,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -703,7 +707,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageBindMountFails) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -721,7 +725,7 @@ TEST_F(IncrementalServiceTest, testCreateStoragePrepareDataLoaderFails) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -735,7 +739,7 @@ TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); mIncrementalService->deleteStorage(storageId); } @@ -750,7 +754,7 @@ TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) { 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 +771,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) { 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 +789,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) { 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 +806,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); mDataLoaderManager->setDataLoaderStatusUnavailable(); } @@ -823,7 +827,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) { 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 +881,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnhealthyStorage) { 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 +976,7 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) { 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 +997,11 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndDisabled) { 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 +1023,7 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChang 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 +1042,7 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsCheckPermissionFails) { 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 +1061,7 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) { 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 +1070,7 @@ TEST_F(IncrementalServiceTest, testMakeDirectory) { 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 +1089,7 @@ TEST_F(IncrementalServiceTest, testMakeDirectories) { 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 +1112,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedFailsWithNoFile) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk")); } @@ -1119,7 +1123,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedFailsWithFailedRanges) { 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 +1135,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccessWithEmptyRanges) { 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 +1147,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccess) { 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 +1159,8 @@ TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithNoFile) { 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 +1170,9 @@ TEST_F(IncrementalServiceTest, testGetLoadingProgressFailsWithFailedRanges) { 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 +1182,9 @@ TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithEmptyRanges) { 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 +1194,9 @@ TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccess) { 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 +1206,7 @@ TEST_F(IncrementalServiceTest, testRegisterLoadingProgressListenerSuccess) { 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 +1231,7 @@ TEST_F(IncrementalServiceTest, testRegisterLoadingProgressListenerFailsToGetProg 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 +1246,10 @@ TEST_F(IncrementalServiceTest, testRegisterStorageHealthListenerSuccess) { 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 +1318,123 @@ TEST_F(IncrementalServiceTest, testRegisterStorageHealthListenerSuccess) { 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 fff7f80ad329..d9350f39ee58 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -237,6 +237,8 @@ public final class SystemServer implements Dumpable { "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 = @@ -680,7 +682,9 @@ public final class SystemServer implements Dumpable { // Load preinstalled system fonts for system server, so that WindowManagerService, etc // can start using Typeface. Note that fonts are required not only for text rendering, // but also for some text operations (e.g. TextUtils.makeSafeForPresentation()). - Typeface.loadPreinstalledSystemFontMap(); + if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { + Typeface.loadPreinstalledSystemFontMap(); + } // Attach JVMTI agent if this is a debuggable build and the system property is set. if (Build.IS_DEBUGGABLE) { @@ -1354,7 +1358,8 @@ public final class SystemServer implements Dumpable { } t.traceBegin("IpConnectivityMetrics"); - mSystemServiceManager.startService(IP_CONNECTIVITY_METRICS_CLASS); + mSystemServiceManager.startServiceFromJar(IP_CONNECTIVITY_METRICS_CLASS, + CONNECTIVITY_SERVICE_APEX_PATH); t.traceEnd(); t.traceBegin("NetworkWatchlistService"); @@ -1719,8 +1724,8 @@ public final class SystemServer implements Dumpable { // 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. diff --git a/services/musicrecognition/OWNERS b/services/musicrecognition/OWNERS new file mode 100644 index 000000000000..58f5d40dd8c3 --- /dev/null +++ b/services/musicrecognition/OWNERS @@ -0,0 +1,6 @@ +# Bug component: 830636 + +joannechung@google.com +oni@google.com +volnov@google.com + diff --git a/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java index 353151214823..0cb729d924b4 100644 --- a/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java +++ b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java @@ -16,6 +16,7 @@ package com.android.server.musicrecognition; +import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_AUDIO_UNAVAILABLE; import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_KILLED; import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_UNAVAILABLE; import static android.media.musicrecognition.MusicRecognitionManager.RecognitionFailureCode; @@ -64,10 +65,6 @@ public final class MusicRecognitionManagerPerUserService extends @GuardedBy("mLock") private RemoteMusicRecognitionService mRemoteService; - private MusicRecognitionServiceCallback mRemoteServiceCallback = - new MusicRecognitionServiceCallback(); - private IMusicRecognitionManagerCallback mCallback; - MusicRecognitionManagerPerUserService( @NonNull MusicRecognitionManagerService primary, @NonNull Object lock, int userId) { @@ -100,7 +97,8 @@ public final class MusicRecognitionManagerPerUserService extends @GuardedBy("mLock") @Nullable - private RemoteMusicRecognitionService ensureRemoteServiceLocked() { + private RemoteMusicRecognitionService ensureRemoteServiceLocked( + IMusicRecognitionManagerCallback clientCallback) { if (mRemoteService == null) { final String serviceName = getComponentNameLocked(); if (serviceName == null) { @@ -113,7 +111,8 @@ public final class MusicRecognitionManagerPerUserService extends mRemoteService = new RemoteMusicRecognitionService(getContext(), serviceComponent, mUserId, this, - mRemoteServiceCallback, mMaster.isBindInstantServiceAllowed(), + new MusicRecognitionServiceCallback(clientCallback), + mMaster.isBindInstantServiceAllowed(), mMaster.verbose); } @@ -130,13 +129,14 @@ public final class MusicRecognitionManagerPerUserService extends @NonNull IBinder callback) { int maxAudioLengthSeconds = Math.min(recognitionRequest.getMaxAudioLengthSeconds(), MAX_STREAMING_SECONDS); - mCallback = IMusicRecognitionManagerCallback.Stub.asInterface(callback); + IMusicRecognitionManagerCallback clientCallback = + IMusicRecognitionManagerCallback.Stub.asInterface(callback); AudioRecord audioRecord = createAudioRecord(recognitionRequest, maxAudioLengthSeconds); - mRemoteService = ensureRemoteServiceLocked(); + mRemoteService = ensureRemoteServiceLocked(clientCallback); if (mRemoteService == null) { try { - mCallback.onRecognitionFailed( + clientCallback.onRecognitionFailed( RECOGNITION_FAILED_SERVICE_UNAVAILABLE); } catch (RemoteException e) { // Ignored. @@ -147,7 +147,8 @@ public final class MusicRecognitionManagerPerUserService extends Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe(); if (clientPipe == null) { try { - mCallback.onAudioStreamClosed(); + clientCallback.onRecognitionFailed( + RECOGNITION_FAILED_AUDIO_UNAVAILABLE); } catch (RemoteException ignored) { // Ignored. } @@ -192,11 +193,10 @@ public final class MusicRecognitionManagerPerUserService extends } finally { audioRecord.release(); try { - mCallback.onAudioStreamClosed(); + clientCallback.onAudioStreamClosed(); } catch (RemoteException ignored) { // Ignored. } - } }); // Send the pipe down to the lookup service while we write to it asynchronously. @@ -207,13 +207,20 @@ public final class MusicRecognitionManagerPerUserService extends * Callback invoked by {@link android.service.musicrecognition.MusicRecognitionService} to pass * back the music search result. */ - private final class MusicRecognitionServiceCallback extends + final class MusicRecognitionServiceCallback extends IMusicRecognitionServiceCallback.Stub { + + private final IMusicRecognitionManagerCallback mClientCallback; + + private MusicRecognitionServiceCallback(IMusicRecognitionManagerCallback clientCallback) { + mClientCallback = clientCallback; + } + @Override public void onRecognitionSucceeded(MediaMetadata result, Bundle extras) { try { sanitizeBundle(extras); - mCallback.onRecognitionSucceeded(result, extras); + mClientCallback.onRecognitionSucceeded(result, extras); } catch (RemoteException ignored) { // Ignored. } @@ -223,18 +230,23 @@ public final class MusicRecognitionManagerPerUserService extends @Override public void onRecognitionFailed(@RecognitionFailureCode int failureCode) { try { - mCallback.onRecognitionFailed(failureCode); + mClientCallback.onRecognitionFailed(failureCode); } catch (RemoteException ignored) { // Ignored. } destroyService(); } + + private IMusicRecognitionManagerCallback getClientCallback() { + return mClientCallback; + } } @Override public void onServiceDied(@NonNull RemoteMusicRecognitionService service) { try { - mCallback.onRecognitionFailed(RECOGNITION_FAILED_SERVICE_KILLED); + service.getServerCallback().getClientCallback().onRecognitionFailed( + RECOGNITION_FAILED_SERVICE_KILLED); } catch (RemoteException e) { // Ignored. } diff --git a/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerService.java b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerService.java index 9123daf0378a..38f43138aee0 100644 --- a/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerService.java +++ b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerService.java @@ -22,7 +22,9 @@ import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.media.musicrecognition.IMusicRecognitionManager; import android.media.musicrecognition.IMusicRecognitionManagerCallback; import android.media.musicrecognition.RecognitionRequest; @@ -32,7 +34,9 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; +import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.FrameworkResourcesServiceNameResolver; @@ -113,9 +117,11 @@ public class MusicRecognitionManagerService extends enforceCaller("beginRecognition"); synchronized (mLock) { + int userId = UserHandle.getCallingUserId(); final MusicRecognitionManagerPerUserService service = getServiceForUserLocked( - UserHandle.getCallingUserId()); - if (service != null) { + userId); + if (service != null && (isDefaultServiceLocked(userId) + || isCalledByServiceAppLocked("beginRecognition"))) { service.beginRecognitionLocked(recognitionRequest, callback); } else { try { @@ -139,5 +145,55 @@ public class MusicRecognitionManagerService extends MusicRecognitionManagerService.this).exec(this, in, out, err, args, callback, resultReceiver); } + + /** True if the currently set handler service is not overridden by the shell. */ + @GuardedBy("mLock") + private boolean isDefaultServiceLocked(int userId) { + final String defaultServiceName = mServiceNameResolver.getDefaultServiceName(userId); + if (defaultServiceName == null) { + return false; + } + + final String currentServiceName = mServiceNameResolver.getServiceName(userId); + return defaultServiceName.equals(currentServiceName); + } + + /** True if the caller of the api is the same app which hosts the default service. */ + @GuardedBy("mLock") + private boolean isCalledByServiceAppLocked(@NonNull String methodName) { + final int userId = UserHandle.getCallingUserId(); + final int callingUid = Binder.getCallingUid(); + final String serviceName = mServiceNameResolver.getServiceName(userId); + if (serviceName == null) { + Slog.e(TAG, methodName + ": called by UID " + callingUid + + ", but there's no service set for user " + userId); + return false; + } + + final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); + if (serviceComponent == null) { + Slog.w(TAG, methodName + ": invalid service name: " + serviceName); + return false; + } + + final String servicePackageName = serviceComponent.getPackageName(); + + final PackageManager pm = getContext().getPackageManager(); + final int serviceUid; + try { + serviceUid = pm.getPackageUidAsUser(servicePackageName, + UserHandle.getCallingUserId()); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, methodName + ": could not verify UID for " + serviceName); + return false; + } + if (callingUid != serviceUid) { + Slog.e(TAG, methodName + ": called by UID " + callingUid + ", but service UID is " + + serviceUid); + return false; + } + + return true; + } } } diff --git a/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java b/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java index 4814a821d525..6c7d673ffe11 100644 --- a/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java +++ b/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java @@ -21,13 +21,13 @@ import android.content.ComponentName; import android.content.Context; import android.media.AudioFormat; import android.media.musicrecognition.IMusicRecognitionService; -import android.media.musicrecognition.IMusicRecognitionServiceCallback; import android.media.musicrecognition.MusicRecognitionService; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.text.format.DateUtils; import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService; +import com.android.server.musicrecognition.MusicRecognitionManagerPerUserService.MusicRecognitionServiceCallback; /** Remote connection to an instance of {@link MusicRecognitionService}. */ public class RemoteMusicRecognitionService extends @@ -39,11 +39,12 @@ public class RemoteMusicRecognitionService extends private static final long TIMEOUT_IDLE_BIND_MILLIS = 40 * DateUtils.SECOND_IN_MILLIS; // Allows the remote service to send back a result. - private final IMusicRecognitionServiceCallback mServerCallback; + private final MusicRecognitionServiceCallback + mServerCallback; public RemoteMusicRecognitionService(Context context, ComponentName serviceName, int userId, MusicRecognitionManagerPerUserService perUserService, - IMusicRecognitionServiceCallback callback, + MusicRecognitionServiceCallback callback, boolean bindInstantServiceAllowed, boolean verbose) { super(context, MusicRecognitionService.ACTION_MUSIC_SEARCH_LOOKUP, serviceName, userId, perUserService, @@ -66,6 +67,10 @@ public class RemoteMusicRecognitionService extends return TIMEOUT_IDLE_BIND_MILLIS; } + MusicRecognitionServiceCallback getServerCallback() { + return mServerCallback; + } + /** * Required, but empty since we don't need to notify the callback implementation of the request * results. diff --git a/services/net/Android.bp b/services/net/Android.bp index 2a296744f5ed..7036ccfbd951 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -13,7 +13,7 @@ java_library_static { ":services.net-sources", ], static_libs: [ - "netd_aidl_interfaces-platform-java", + "netd-client", "netlink-client", "networkstack-client", "net-utils-services-common", diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java index 49a41f02c484..16b9165915e2 100644 --- a/services/people/java/com/android/server/people/PeopleService.java +++ b/services/people/java/com/android/server/people/PeopleService.java @@ -19,7 +19,9 @@ package com.android.server.people; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.people.ConversationChannel; +import android.app.people.ConversationStatus; import android.app.people.IPeopleManager; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionSessionId; @@ -27,6 +29,7 @@ import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.prediction.IPredictionCallback; import android.content.Context; +import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.os.Binder; import android.os.CancellationSignal; @@ -38,9 +41,11 @@ import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.people.data.DataManager; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -54,6 +59,8 @@ public class PeopleService extends SystemService { private final DataManager mDataManager; + private PackageManagerInternal mPackageManagerInternal; + /** * Initializes the system service. * @@ -83,6 +90,7 @@ public class PeopleService extends SystemService { publishBinderService(Context.PEOPLE_SERVICE, mService); } publishLocalService(PeopleServiceInternal.class, new LocalService()); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); } @Override @@ -112,6 +120,26 @@ public class PeopleService extends SystemService { return UserHandle.isSameApp(uid, Process.SYSTEM_UID) || uid == Process.ROOT_UID; } + private int handleIncomingUser(int userId) { + try { + return ActivityManager.getService().handleIncomingUser( + Binder.getCallingPid(), Binder.getCallingUid(), userId, true, true, "", null); + } catch (RemoteException re) { + // Shouldn't happen, local. + } + return userId; + } + + private void checkCallerIsSameApp(String pkg) { + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getUserId(callingUid); + + if (mPackageManagerInternal.getPackageUid(pkg, /*flags=*/ 0, + callingUserId) != callingUid) { + throw new SecurityException("Calling uid " + callingUid + " cannot query events" + + "for package " + pkg); + } + } /** * Enforces that only the system, root UID or SystemUI can make certain calls. @@ -154,6 +182,40 @@ public class PeopleService extends SystemService { enforceSystemRootOrSystemUI(getContext(), "get last interaction"); return mDataManager.getLastInteraction(packageName, userId, shortcutId); } + + @Override + public void addOrUpdateStatus(String packageName, int userId, String conversationId, + ConversationStatus status) { + handleIncomingUser(userId); + checkCallerIsSameApp(packageName); + mDataManager.addOrUpdateStatus(packageName, userId, conversationId, status); + } + + @Override + public void clearStatus(String packageName, int userId, String conversationId, + String statusId) { + handleIncomingUser(userId); + checkCallerIsSameApp(packageName); + mDataManager.clearStatus(packageName, userId, conversationId, statusId); + } + + @Override + public void clearStatuses(String packageName, int userId, String conversationId) { + handleIncomingUser(userId); + checkCallerIsSameApp(packageName); + mDataManager.clearStatuses(packageName, userId, conversationId); + } + + @Override + public ParceledListSlice<ConversationStatus> getStatuses(String packageName, int userId, + String conversationId) { + handleIncomingUser(userId); + if (!isSystemOrRoot()) { + checkCallerIsSameApp(packageName); + } + return new ParceledListSlice<>( + mDataManager.getStatuses(packageName, userId, conversationId)); + } }; @VisibleForTesting diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java index 45f389cbd3ff..16c4c29a798e 100644 --- a/services/people/java/com/android/server/people/data/ConversationInfo.java +++ b/services/people/java/com/android/server/people/data/ConversationInfo.java @@ -19,6 +19,7 @@ package com.android.server.people.data; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.people.ConversationStatus; import android.content.LocusId; import android.content.LocusIdProto; import android.content.pm.ShortcutInfo; @@ -39,6 +40,10 @@ import java.io.DataOutputStream; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -101,6 +106,8 @@ public class ConversationInfo { @ConversationFlags private int mConversationFlags; + private Map<String, ConversationStatus> mCurrStatuses; + private ConversationInfo(Builder builder) { mShortcutId = builder.mShortcutId; mLocusId = builder.mLocusId; @@ -111,6 +118,7 @@ public class ConversationInfo { mLastEventTimestamp = builder.mLastEventTimestamp; mShortcutFlags = builder.mShortcutFlags; mConversationFlags = builder.mConversationFlags; + mCurrStatuses = builder.mCurrStatuses; } @NonNull @@ -213,6 +221,10 @@ public class ConversationInfo { return hasConversationFlags(FLAG_CONTACT_STARRED); } + public Collection<ConversationStatus> getStatuses() { + return mCurrStatuses.values(); + } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -230,14 +242,15 @@ public class ConversationInfo { && Objects.equals(mParentNotificationChannelId, other.mParentNotificationChannelId) && Objects.equals(mLastEventTimestamp, other.mLastEventTimestamp) && mShortcutFlags == other.mShortcutFlags - && mConversationFlags == other.mConversationFlags; + && mConversationFlags == other.mConversationFlags + && Objects.equals(mCurrStatuses, other.mCurrStatuses); } @Override public int hashCode() { return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber, mNotificationChannelId, mParentNotificationChannelId, mLastEventTimestamp, - mShortcutFlags, mConversationFlags); + mShortcutFlags, mConversationFlags, mCurrStatuses); } @Override @@ -251,6 +264,7 @@ public class ConversationInfo { sb.append(", notificationChannelId=").append(mNotificationChannelId); sb.append(", parentNotificationChannelId=").append(mParentNotificationChannelId); sb.append(", lastEventTimestamp=").append(mLastEventTimestamp); + sb.append(", statuses=").append(mCurrStatuses); sb.append(", shortcutFlags=0x").append(Integer.toHexString(mShortcutFlags)); sb.append(" ["); if (isShortcutLongLived()) { @@ -321,6 +335,7 @@ public class ConversationInfo { protoOutputStream.write(ConversationInfoProto.CONTACT_PHONE_NUMBER, mContactPhoneNumber); } + // ConversationStatus is a transient object and not persisted } @Nullable @@ -337,6 +352,7 @@ public class ConversationInfo { out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : ""); out.writeUTF(mParentNotificationChannelId != null ? mParentNotificationChannelId : ""); out.writeLong(mLastEventTimestamp); + // ConversationStatus is a transient object and not persisted } catch (IOException e) { Slog.e(TAG, "Failed to write fields to backup payload.", e); return null; @@ -469,6 +485,8 @@ public class ConversationInfo { @ConversationFlags private int mConversationFlags; + private Map<String, ConversationStatus> mCurrStatuses = new HashMap<>(); + Builder() { } @@ -486,6 +504,7 @@ public class ConversationInfo { mLastEventTimestamp = conversationInfo.mLastEventTimestamp; mShortcutFlags = conversationInfo.mShortcutFlags; mConversationFlags = conversationInfo.mConversationFlags; + mCurrStatuses = conversationInfo.mCurrStatuses; } Builder setShortcutId(@NonNull String shortcutId) { @@ -579,6 +598,26 @@ public class ConversationInfo { return this; } + Builder setStatuses(List<ConversationStatus> statuses) { + mCurrStatuses.clear(); + if (statuses != null) { + for (ConversationStatus status : statuses) { + mCurrStatuses.put(status.getId(), status); + } + } + return this; + } + + Builder addOrUpdateStatus(ConversationStatus status) { + mCurrStatuses.put(status.getId(), status); + return this; + } + + Builder clearStatus(String statusId) { + mCurrStatuses.remove(statusId); + return this; + } + ConversationInfo build() { Objects.requireNonNull(mShortcutId); return new ConversationInfo(this); diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index b5e595a42ca9..e04e287d80f6 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -26,6 +26,7 @@ import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.app.Person; import android.app.people.ConversationChannel; +import android.app.people.ConversationStatus; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.usage.UsageEvents; @@ -75,6 +76,7 @@ import com.android.server.notification.NotificationManagerInternal; import com.android.server.notification.ShortcutHelper; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; @@ -308,6 +310,73 @@ public class DataManager { return 0L; } + public void addOrUpdateStatus(String packageName, int userId, String conversationId, + ConversationStatus status) { + ConversationStore cs = getConversationStoreOrThrow(packageName, userId); + ConversationInfo convToModify = getConversationInfoOrThrow(cs, conversationId); + ConversationInfo.Builder builder = new ConversationInfo.Builder(convToModify); + builder.addOrUpdateStatus(status); + cs.addOrUpdate(builder.build()); + } + + public void clearStatus(String packageName, int userId, String conversationId, + String statusId) { + ConversationStore cs = getConversationStoreOrThrow(packageName, userId); + ConversationInfo convToModify = getConversationInfoOrThrow(cs, conversationId); + ConversationInfo.Builder builder = new ConversationInfo.Builder(convToModify); + builder.clearStatus(statusId); + cs.addOrUpdate(builder.build()); + } + + public void clearStatuses(String packageName, int userId, String conversationId) { + ConversationStore cs = getConversationStoreOrThrow(packageName, userId); + ConversationInfo convToModify = getConversationInfoOrThrow(cs, conversationId); + ConversationInfo.Builder builder = new ConversationInfo.Builder(convToModify); + builder.setStatuses(null); + cs.addOrUpdate(builder.build()); + } + + public @NonNull List<ConversationStatus> getStatuses(String packageName, int userId, + String conversationId) { + ConversationStore cs = getConversationStoreOrThrow(packageName, userId); + ConversationInfo conversationInfo = getConversationInfoOrThrow(cs, conversationId); + Collection<ConversationStatus> statuses = conversationInfo.getStatuses(); + if (statuses != null) { + final ArrayList<ConversationStatus> list = new ArrayList<>(statuses.size()); + list.addAll(statuses); + return list; + } + return new ArrayList<>(); + } + + /** + * Returns a conversation store for a package, if it exists. + */ + private @NonNull ConversationStore getConversationStoreOrThrow(String packageName, int userId) { + final PackageData packageData = getPackage(packageName, userId); + if (packageData == null) { + throw new IllegalArgumentException("No settings exist for package " + packageName); + } + ConversationStore cs = packageData.getConversationStore(); + if (cs == null) { + throw new IllegalArgumentException("No conversations exist for package " + packageName); + } + return cs; + } + + /** + * Returns a conversation store for a package, if it exists. + */ + private @NonNull ConversationInfo getConversationInfoOrThrow(ConversationStore cs, + String conversationId) { + ConversationInfo ci = cs.getConversation(conversationId); + + if (ci == null) { + throw new IllegalArgumentException("Conversation does not exist"); + } + return ci; + } + /** Reports the sharing related {@link AppTargetEvent} from App Prediction Manager. */ public void reportShareTargetEvent(@NonNull AppTargetEvent event, @NonNull IntentFilter intentFilter) { diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java index 3220dff553d3..a691a8d44e48 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 @@ public class AppStateTrackerTest { } } - 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { } @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 @@ public class AppStateTrackerTest { 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); - - areRestrictedWithExemption(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); - areRestrictedWithExemption(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); + + 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { // ------------------------------------------------------------------------- // 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 @@ public class AppStateTrackerTest { 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 e779e21bb987..c0f0ce047da6 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 f375421043fd..fd364ae77240 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 @@ public class RescuePartyTest { verifiedTimesMap); noteBoot(4); + assertTrue(RescueParty.isRebootPropertySet()); - assertTrue(RescueParty.isAttemptingFactoryReset()); + noteBoot(5); + assertTrue(RescueParty.isFactoryResetPropertySet()); } @Test @@ -255,7 +257,10 @@ public class RescuePartyTest { /*configResetVerifiedTimesMap=*/ null); notePersistentAppCrash(4); - assertTrue(RescueParty.isAttemptingFactoryReset()); + assertTrue(RescueParty.isRebootPropertySet()); + + notePersistentAppCrash(5); + assertTrue(RescueParty.isFactoryResetPropertySet()); } @Test @@ -306,7 +311,11 @@ public class RescuePartyTest { 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 @@ public class RescuePartyTest { 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 @@ public class RescuePartyTest { noteBoot(i + 1); } assertTrue(RescueParty.isAttemptingFactoryReset()); + assertTrue(RescueParty.isFactoryResetPropertySet()); } @Test @@ -424,7 +438,7 @@ public class RescuePartyTest { 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 8edac4f8ce58..7a970a1c3d46 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_LISTENE 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.argThat; 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 @@ public class AlarmManagerServiceTest { } @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 @@ public class AlarmManagerServiceTest { 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 @@ public class AlarmManagerServiceTest { } @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 @@ public class AlarmManagerServiceTest { } @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 @@ public class AlarmManagerServiceTest { } @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/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java new file mode 100644 index 000000000000..0633ab92acf8 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java @@ -0,0 +1,95 @@ +/* + * 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.am; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.am.ActiveServices.FGS_BG_START_USE_EXEMPTION_LIST_CHANGE_ID; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; + +import android.app.compat.CompatChanges; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + + + +@RunWith(AndroidJUnit4.class) +public class ActiveServicesTest { + + private MockitoSession mMockingSession; + + @Before + public void setUp() { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .mockStatic(CompatChanges.class) + .startMocking(); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + } + + private void checkPackageExempted(String pkg, int uid, boolean expected) { + assertEquals("Package=" + pkg + " uid=" + uid, + expected, ActiveServices.isPackageExemptedFromFgsRestriction(pkg, uid)); + } + + @Test + public void isPackageExemptedFromFgsRestriction() { + // Compat changes are enabled by default. + when(CompatChanges.isChangeEnabled(anyLong(), anyInt())).thenReturn(true); + + checkPackageExempted("", 1, false); + checkPackageExempted("abc", 1, false); + checkPackageExempted("com.random", 1, false); + + // This package is exempted but not its subpackages. + checkPackageExempted("com.google.pixel.exo.bootstrapping", 1, true); + checkPackageExempted("com.google.pixel.exo.bootstrapping.subpackage", 1, false); + + // Subpackages are also exempted. + checkPackageExempted("com.android.webview", 1, true); + checkPackageExempted("com.android.webview.beta", 1, true); + checkPackageExempted("com.chrome", 1, true); + checkPackageExempted("com.chrome.canary", 1, true); + + checkPackageExempted("com.android.webviewx", 1, false); + + // Now toggle the compat ID for a specific UID. + when(CompatChanges.isChangeEnabled(FGS_BG_START_USE_EXEMPTION_LIST_CHANGE_ID, 10)) + .thenReturn(false); + // Exempted package, but compat id is disabled for the UID. + checkPackageExempted("com.android.webview", 10, false); + + // Exempted package, but compat id is still enabled for the UID. + checkPackageExempted("com.android.webview", 11, true); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java index 829e3124dc39..c4d14f947cda 100644 --- a/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; +import android.app.admin.DevicePolicySafetyChecker; import android.content.Context; import android.content.pm.PackageManager; import android.os.RecoverySystem; @@ -35,6 +36,8 @@ import android.platform.test.annotations.Presubmit; import android.service.persistentdata.PersistentDataBlockManager; import android.util.Log; +import com.android.internal.os.IResultReceiver; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -58,6 +61,7 @@ public final class FactoryResetterTest { private @Mock StorageManager mSm; private @Mock PersistentDataBlockManager mPdbm; private @Mock UserManager mUm; + private @Mock DevicePolicySafetyChecker mSafetyChecker; @Before public void startSession() { @@ -69,7 +73,7 @@ public final class FactoryResetterTest { when(mContext.getSystemService(any(Class.class))).thenAnswer((inv) -> { Log.d(TAG, "Mocking " + inv); - Class serviceClass = (Class) inv.getArguments()[0]; + Class<?> serviceClass = (Class<?>) inv.getArguments()[0]; if (serviceClass.equals(PersistentDataBlockManager.class)) return mPdbm; if (serviceClass.equals(StorageManager.class)) return mSm; if (serviceClass.equals(UserManager.class)) return mUm; @@ -194,6 +198,44 @@ public final class FactoryResetterTest { verifyRebootWipeUserDataAllArgsCalled(); } + @Test + public void testFactoryReset_minimumArgs_safetyChecker_neverReplied() throws Exception { + allowFactoryReset(); + + FactoryResetter.newBuilder(mContext).setSafetyChecker(mSafetyChecker).build() + .factoryReset(); + + verifyWipeAdoptableStorageNotCalled(); + verifyWipeFactoryResetProtectionNotCalled(); + verifyRebootWipeUserDataNotCalled(); + } + + @Test + public void testFactoryReset_allArgs_safetyChecker_replied() throws Exception { + allowFactoryReset(); + + doAnswer((inv) -> { + Log.d(TAG, "Mocking " + inv); + IResultReceiver receiver = (IResultReceiver) inv.getArguments()[0]; + receiver.send(0, null); + return null; + }).when(mSafetyChecker).onFactoryReset(any()); + + FactoryResetter.newBuilder(mContext) + .setSafetyChecker(mSafetyChecker) + .setReason(REASON) + .setForce(true) + .setShutdown(true) + .setWipeEuicc(true) + .setWipeAdoptableStorage(true) + .setWipeFactoryResetProtection(true) + .build().factoryReset(); + + verifyWipeAdoptableStorageCalled(); + verifyWipeFactoryResetProtectionCalled(); + verifyRebootWipeUserDataAllArgsCalled(); + } + private void revokeMasterClearPermission() { when(mContext.checkCallingOrSelfPermission(android.Manifest.permission.MASTER_CLEAR)) .thenReturn(PackageManager.PERMISSION_DENIED); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index 78bcc13c9265..4740df5a2a44 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -383,27 +383,6 @@ public class ConnectivityControllerTest { } @Test - public void testWouldBeReadyWithConnectivityLocked() { - final ConnectivityController controller = spy(new ConnectivityController(mService)); - final JobStatus red = createJobStatus(createJob() - .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) - .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); - - doReturn(false).when(controller).isNetworkAvailable(any()); - assertFalse(controller.wouldBeReadyWithConnectivityLocked(red)); - - doReturn(true).when(controller).isNetworkAvailable(any()); - doReturn(false).when(controller).wouldBeReadyWithConstraintLocked(any(), - eq(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(controller.wouldBeReadyWithConnectivityLocked(red)); - - doReturn(true).when(controller).isNetworkAvailable(any()); - doReturn(true).when(controller).wouldBeReadyWithConstraintLocked(any(), - eq(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(controller.wouldBeReadyWithConnectivityLocked(red)); - } - - @Test public void testEvaluateStateLocked_JobWithoutConnectivity() { final ConnectivityController controller = new ConnectivityController(mService); final JobStatus red = createJobStatus(createJob().setMinimumLatency(1)); @@ -417,7 +396,9 @@ public class ConnectivityControllerTest { @Test public void testEvaluateStateLocked_JobWouldBeReady() { final ConnectivityController controller = spy(new ConnectivityController(mService)); - doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(any()); + doReturn(true).when(controller) + .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + doReturn(true).when(controller).isNetworkAvailable(any()); final JobStatus red = createJobStatus(createJob() .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); @@ -455,7 +436,8 @@ public class ConnectivityControllerTest { @Test public void testEvaluateStateLocked_JobWouldNotBeReady() { final ConnectivityController controller = spy(new ConnectivityController(mService)); - doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(any()); + doReturn(false).when(controller) + .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); final JobStatus red = createJobStatus(createJob() .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); @@ -523,21 +505,26 @@ public class ConnectivityControllerTest { .setAppIdleWhitelist(eq(12345), anyBoolean()); // Both jobs would still be ready. Exception should not be revoked. - doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(any()); + doReturn(true).when(controller) + .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + doReturn(true).when(controller).isNetworkAvailable(any()); controller.reevaluateStateLocked(UID_RED); inOrder.verify(mNetPolicyManagerInternal, never()) .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); // One job is still ready. Exception should not be revoked. - doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(eq(redOne)); - doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(eq(redTwo)); + doReturn(true).when(controller).wouldBeReadyWithConstraintLocked( + eq(redOne), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + doReturn(false).when(controller).wouldBeReadyWithConstraintLocked( + eq(redTwo), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); controller.reevaluateStateLocked(UID_RED); inOrder.verify(mNetPolicyManagerInternal, never()) .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED)); // Both jobs are not ready. Exception should be revoked. - doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(any()); + doReturn(false).when(controller) + .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); controller.reevaluateStateLocked(UID_RED); inOrder.verify(mNetPolicyManagerInternal, times(1)) .setAppIdleWhitelist(eq(UID_RED), eq(false)); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 05f1ed8a20b3..1a65894f85b1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -136,6 +136,8 @@ public class QuotaControllerTest { @Mock private JobSchedulerService mJobSchedulerService; @Mock + private PackageManagerInternal mPackageManagerInternal; + @Mock private UsageStatsManagerInternal mUsageStatsManager; private JobStore mJobStore; @@ -172,7 +174,7 @@ public class QuotaControllerTest { doReturn(mUsageStatsManager) .when(() -> LocalServices.getService(UsageStatsManagerInternal.class)); // Used in JobStatus. - doReturn(mock(PackageManagerInternal.class)) + doReturn(mPackageManagerInternal) .when(() -> LocalServices.getService(PackageManagerInternal.class)); // Used in QuotaController.Handler. mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir()); @@ -2377,6 +2379,7 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 1 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 30 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 27 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, 10 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, 12 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 87 * SECOND_IN_MILLIS); @@ -2414,6 +2417,7 @@ public class QuotaControllerTest { assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]); assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]); assertEquals(27 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]); + assertEquals(10 * HOUR_IN_MILLIS, mQuotaController.getEjLimitSpecialAdditionMs()); assertEquals(12 * HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs()); assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJTopAppTimeChunkSizeMs()); assertEquals(87 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs()); @@ -2452,6 +2456,7 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, -1); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, -1); @@ -2486,6 +2491,7 @@ public class QuotaControllerTest { assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]); assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]); assertEquals(0, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]); + assertEquals(0, mQuotaController.getEjLimitSpecialAdditionMs()); assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs()); assertEquals(1, mQuotaController.getEJTopAppTimeChunkSizeMs()); assertEquals(10 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs()); @@ -2518,6 +2524,7 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 25 * HOUR_IN_MILLIS); @@ -2542,6 +2549,7 @@ public class QuotaControllerTest { assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]); + assertEquals(0, mQuotaController.getEjLimitSpecialAdditionMs()); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs()); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJTopAppTimeChunkSizeMs()); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardTopAppMs()); @@ -3498,6 +3506,37 @@ public class QuotaControllerTest { } @Test + public void testGetRemainingEJExecutionTimeLocked_SpecialApp() { + doReturn(new String[]{SOURCE_PACKAGE}).when(mPackageManagerInternal) + .getKnownPackageNames(eq(PackageManagerInternal.PACKAGE_VERIFIER), anyInt()); + mQuotaController.onSystemServicesReady(); + + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - mQcConstants.EJ_WINDOW_SIZE_MS, MINUTE_IN_MILLIS, 5), + true); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); + + final long[] limits = mQuotaController.getEJLimitsMs(); + for (int i = 0; i < limits.length; ++i) { + setStandbyBucket(i); + assertEquals("Got wrong remaining EJ execution time for bucket #" + i, + i == NEVER_INDEX ? 0 + : (limits[i] + mQuotaController.getEjLimitSpecialAdditionMs() + - 5 * MINUTE_IN_MILLIS), + mQuotaController.getRemainingEJExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + } + } + + @Test public void testGetRemainingEJExecutionTimeLocked_OneSessionStraddlesEdge() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); final long[] limits = mQuotaController.getEJLimitsMs(); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java index 9d847153661f..d64c1b31e343 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java @@ -711,25 +711,31 @@ public class TimeControllerTest { .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); // Test evaluating something before the current deadline. + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + mTimeController.evaluateStateLocked(jobEarliest); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), eq(TAG_DELAY), any(), any(), any()); doReturn(true).when(mTimeController) .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); mTimeController.evaluateStateLocked(jobEarliest); inOrder.verify(mAlarmManager, times(1)) .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(), any(), any()); - // Job goes back to not being ready. Middle is still true, so use that alarm. + // Job goes back to not being ready. Middle is still true, but we don't check and actively + // defer alarm. doReturn(false).when(mTimeController) .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); mTimeController.evaluateStateLocked(jobEarliest); - inOrder.verify(mAlarmManager, times(1)) - .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), eq(TAG_DELAY), any(), any(), any()); - // Turn middle off. Latest is true, so use that alarm. + // Turn middle off. Latest is true, but we don't check and actively defer alarm. doReturn(false).when(mTimeController) .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt()); mTimeController.evaluateStateLocked(jobMiddle); - inOrder.verify(mAlarmManager, times(1)) - .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), eq(TAG_DELAY), any(), any(), any()); } @@ -768,25 +774,32 @@ public class TimeControllerTest { .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any()); // Test evaluating something before the current deadline. + doReturn(false).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); + mTimeController.evaluateStateLocked(jobEarliest); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), + eq(TAG_DEADLINE), any(), any(), any()); doReturn(true).when(mTimeController) .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); mTimeController.evaluateStateLocked(jobEarliest); inOrder.verify(mAlarmManager, times(1)) .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE), any(), any(), any()); - // Job goes back to not being ready. Middle is still true, so use that alarm. + // Job goes back to not being ready. Middle is still true, but we don't check and actively + // defer alarm. doReturn(false).when(mTimeController) .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt()); mTimeController.evaluateStateLocked(jobEarliest); - inOrder.verify(mAlarmManager, times(1)) - .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), eq(TAG_DEADLINE), any(), any(), any()); - // Turn middle off. Latest is true, so use that alarm. + // Turn middle off. Latest is true, but we don't check and actively defer alarm. doReturn(false).when(mTimeController) .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt()); mTimeController.evaluateStateLocked(jobMiddle); - inOrder.verify(mAlarmManager, times(1)) - .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), anyLong(), anyLong(), eq(TAG_DEADLINE), any(), any(), any()); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java index df7a445a068f..84bfc9be3d41 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java @@ -19,10 +19,8 @@ package com.android.server.location.provider; import static android.app.AppOpsManager.OP_FINE_LOCATION; import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; import static android.app.AppOpsManager.OP_MONITOR_LOCATION; -import static android.location.Criteria.ACCURACY_COARSE; -import static android.location.Criteria.ACCURACY_FINE; -import static android.location.Criteria.POWER_HIGH; import static android.location.LocationRequest.PASSIVE_INTERVAL; +import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH; import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF; import static androidx.test.ext.truth.location.LocationSubject.assertThat; @@ -66,7 +64,8 @@ import android.location.LocationManagerInternal; import android.location.LocationManagerInternal.ProviderEnabledListener; import android.location.LocationRequest; import android.location.LocationResult; -import android.location.ProviderProperties; +import android.location.provider.ProviderProperties; +import android.location.provider.ProviderRequest; import android.location.util.identity.CallerIdentity; import android.os.Bundle; import android.os.ICancellationSignal; @@ -81,7 +80,6 @@ import android.util.Log; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.location.ProviderRequest; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.location.injector.FakeUserInfoHelper; @@ -115,8 +113,13 @@ public class LocationProviderManagerTest { private static final int OTHER_USER = CURRENT_USER + 10; private static final String NAME = "test"; - private static final ProviderProperties PROPERTIES = new ProviderProperties(false, false, false, - false, true, true, true, POWER_HIGH, ACCURACY_FINE); + private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder() + .setHasAltitudeSupport(true) + .setHasSpeedSupport(true) + .setHasBearingSupport(true) + .setPowerUsage(POWER_USAGE_HIGH) + .setAccuracy(ProviderProperties.ACCURACY_FINE) + .build(); private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1, "mypackage", "attribution"); @@ -189,8 +192,14 @@ public class LocationProviderManagerTest { assertThat(mManager.getIdentity()).isEqualTo(IDENTITY); assertThat(mManager.hasProvider()).isTrue(); - ProviderProperties newProperties = new ProviderProperties(true, true, true, - true, false, false, false, POWER_HIGH, ACCURACY_COARSE); + ProviderProperties newProperties = new ProviderProperties.Builder() + .setHasNetworkRequirement(true) + .setHasSatelliteRequirement(true) + .setHasCellRequirement(true) + .setHasMonetaryCost(true) + .setPowerUsage(POWER_USAGE_HIGH) + .setAccuracy(ProviderProperties.ACCURACY_COARSE) + .build(); mProvider.setProperties(newProperties); assertThat(mManager.getProperties()).isEqualTo(newProperties); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java index daa8a22ccebb..99846c517b7a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java @@ -15,7 +15,9 @@ */ package com.android.server.location.provider; -import static com.android.internal.location.ProviderRequest.EMPTY_REQUEST; +import static android.location.provider.ProviderProperties.ACCURACY_FINE; +import static android.location.provider.ProviderProperties.POWER_USAGE_LOW; +import static android.location.provider.ProviderRequest.EMPTY_REQUEST; import static com.google.common.truth.Truth.assertThat; @@ -24,17 +26,16 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertThrows; -import android.location.Criteria; import android.location.Location; import android.location.LocationResult; -import android.location.ProviderProperties; +import android.location.provider.ProviderProperties; +import android.location.provider.ProviderRequest; import android.location.util.identity.CallerIdentity; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.location.ProviderRequest; import com.android.server.location.test.FakeProvider; import com.android.server.location.test.ProviderListenerCapture; @@ -62,16 +63,14 @@ public class MockableLocationProviderTest { mRealMock = mock(FakeProvider.FakeProviderInterface.class); mRealProvider = new FakeProvider(mRealMock); - mMockProvider = new MockLocationProvider(new ProviderProperties( - false, - false, - false, - false, - true, - true, - true, - Criteria.POWER_LOW, - Criteria.ACCURACY_FINE), + mMockProvider = new MockLocationProvider( + new ProviderProperties.Builder() + .setHasAltitudeSupport(true) + .setHasSpeedSupport(true) + .setHasBearingSupport(true) + .setPowerUsage(POWER_USAGE_LOW) + .setAccuracy(ACCURACY_FINE) + .build(), CallerIdentity.forTest(0, 1, "testpackage", "test")); mProvider = new MockableLocationProvider(lock); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java index 1eb03862f09c..775bdd580157 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java @@ -16,9 +16,9 @@ package com.android.server.location.test; +import android.location.provider.ProviderRequest; import android.os.Bundle; -import com.android.internal.location.ProviderRequest; import com.android.server.location.provider.AbstractLocationProvider; import java.io.FileDescriptor; 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 000000000000..c2e27e084c8c --- /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 f29b0594db8f..c6fde87f5953 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 static org.testng.Assert.expectThrows; 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.PropertyConfigProto; 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 @@ public class AppSearchImplTest { 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 @@ public class AppSearchImplTest { .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 @@ public class AppSearchImplTest { 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 7c68c6be4883..a3f0f6bf280e 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 com.android.server.appsearch.proto.SnippetProto; import org.junit.Test; +import java.util.Collections; + public class SnippetTest { // TODO(tytytyww): Add tests for Double and Long Snippets. @@ -83,7 +85,8 @@ public class SnippetTest { // 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 @@ public class SnippetTest { 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 @@ public class SnippetTest { // 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/compat/CompatConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java index 4f4aa3f16f09..f00edcc85404 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 @@ class CompatConfigBuilder { } 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 3f65a4621778..a1b2dc8bd82d 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.times; import static org.testng.Assert.assertThrows; +import android.compat.Compatibility.ChangeConfig; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -35,6 +36,7 @@ import android.os.Build; import androidx.test.runner.AndroidJUnit4; import com.android.internal.compat.AndroidBuildClassifier; +import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; import com.android.server.LocalServices; @@ -44,6 +46,9 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.HashSet; +import java.util.Set; + @RunWith(AndroidJUnit4.class) public class PlatformCompatTest { private static final String PACKAGE_NAME = "my.package"; @@ -70,6 +75,8 @@ public class PlatformCompatTest { new PackageManager.NameNotFoundException()); when(mPackageManagerInternal.getPackageUid(eq(PACKAGE_NAME), eq(0), anyInt())) .thenReturn(-1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenThrow(new PackageManager.NameNotFoundException()); mCompatConfig = new CompatConfig(mBuildClassifier, mContext); mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); // Assume userdebug/eng non-final build @@ -90,17 +97,22 @@ public class PlatformCompatTest { .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 @@ -116,12 +128,44 @@ public class PlatformCompatTest { .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 + public void testOverrideAtInstallTime() throws Exception { + mCompatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addEnabledChangeWithId(1L) + .addDisabledChangeWithId(2L) + .addEnableAfterSdkChangeWithId(Build.VERSION_CODES.O, 3L) + .build(); + mCompatConfig.forceNonDebuggableFinalForTest(true); + mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); + + // Before adding overrides. + assertThat(mPlatformCompat.isChangeEnabledByPackageName(1, PACKAGE_NAME, 0)).isTrue(); + assertThat(mPlatformCompat.isChangeEnabledByPackageName(2, PACKAGE_NAME, 0)).isFalse(); + assertThat(mPlatformCompat.isChangeEnabledByPackageName(3, PACKAGE_NAME, 0)).isTrue(); + + // Add overrides. + Set<Long> enabled = new HashSet<>(); + enabled.add(2L); + Set<Long> disabled = new HashSet<>(); + disabled.add(1L); + disabled.add(3L); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + mPlatformCompat.setOverridesForTest(compatibilityChangeConfig, PACKAGE_NAME); + + // After adding overrides. + assertThat(mPlatformCompat.isChangeEnabledByPackageName(1, PACKAGE_NAME, 0)).isFalse(); + assertThat(mPlatformCompat.isChangeEnabledByPackageName(2, PACKAGE_NAME, 0)).isTrue(); + assertThat(mPlatformCompat.isChangeEnabledByPackageName(3, PACKAGE_NAME, 0)).isFalse(); } @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 17324bab70d2..9c28c99fcc07 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 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @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 1c7da3b98930..a455ba90ccfe 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -116,7 +116,9 @@ import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; import org.mockito.internal.util.collections.Sets; @@ -206,6 +208,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { private static final String PROFILE_OFF_SUSPENSION_TEXT = "suspension_text"; private static final String PROFILE_OFF_SUSPENSION_SOON_TEXT = "suspension_tomorrow_text"; + @BeforeClass + public static void setUpClass() { + Notification.DevFlags.sForceDefaults = true; + } + + @AfterClass + public static void tearDownClass() { + Notification.DevFlags.sForceDefaults = false; + } + @Before public void setUp() throws Exception { @@ -1616,6 +1628,33 @@ public class DevicePolicyManagerTest extends DpmTestBase { )), 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. @@ -6879,6 +6918,35 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index 1d2dcaecf978..6068fdf9b5b5 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -17,6 +17,7 @@ package com.android.server.devicepolicy; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.app.AppOpsManager; @@ -33,6 +34,7 @@ import android.os.Handler; import android.os.UserHandle; import android.test.mock.MockContext; import android.util.ArrayMap; +import android.util.DisplayMetrics; import android.util.ExceptionUtils; import androidx.annotation.NonNull; @@ -174,6 +176,11 @@ public class DpmMockContext extends MockContext { binder = new MockBinder(); resources = mock(Resources.class); spiedContext = mock(Context.class); + + // Set up density for notification building + DisplayMetrics displayMetrics = mock(DisplayMetrics.class); + displayMetrics.density = 2.25f; + when(resources.getDisplayMetrics()).thenReturn(displayMetrics); } @Override 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 000000000000..c2c1d5b4f3be --- /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/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index ec747acd376d..23a4c2f417c5 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -53,21 +53,29 @@ public class AutomaticBrightnessControllerTest { private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 0; private static final float DOZE_SCALE_FACTOR = 0.0f; private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false; + private static final int DISPLAY_ID = 0; + private static final int LAYER_STACK = 0; private Context mContext; + private LogicalDisplay mLogicalDisplay; + @Mock SensorManager mSensorManager; @Mock BrightnessMappingStrategy mBrightnessMappingStrategy; @Mock HysteresisLevels mAmbientBrightnessThresholds; @Mock HysteresisLevels mScreenBrightnessThresholds; - @Mock Handler mNoopHandler; + @Mock Handler mNoOpHandler; @Mock DisplayDeviceConfig mDisplayDeviceConfig; + @Mock DisplayDevice mDisplayDevice; private static final int LIGHT_SENSOR_WARMUP_TIME = 0; @Before public void setUp() { + // Share classloader to allow package private access. + System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); mContext = InstrumentationRegistry.getContext(); + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice); } private AutomaticBrightnessController setupController(Sensor lightSensor) { @@ -75,7 +83,7 @@ public class AutomaticBrightnessControllerTest { new AutomaticBrightnessController.Injector() { @Override public Handler getBackgroundThreadHandler() { - return mNoopHandler; + return mNoOpHandler; } }, () -> { }, mContext.getMainLooper(), mSensorManager, lightSensor, @@ -83,7 +91,7 @@ public class AutomaticBrightnessControllerTest { BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE, INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG, DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, - mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mContext + mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mLogicalDisplay, mContext ); controller.setLoggingEnabled(true); 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 000000000000..34ac813f02e0 --- /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/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index ae5960952738..eb2f9608f6f3 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -24,6 +24,7 @@ import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; import static com.android.server.hdmi.Constants.MESSAGE_DEVICE_VENDOR_ID; import static com.android.server.hdmi.Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS; +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.google.common.truth.Truth.assertThat; @@ -32,6 +33,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiPortInfo; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; @@ -43,6 +45,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -53,6 +56,8 @@ import java.util.List; /** Tests for {@link HdmiCecLocalDevice} class. */ public class HdmiCecLocalDeviceTest { + private FakeNativeWrapper mNativeWrapper; + private static int SendCecCommandFactory(int srcAddress, int dstAddress, byte[] body) { switch (body[0] & 0xFF) { /** {@link Constants#MESSAGE_GIVE_PHYSICAL_ADDRESS} */ @@ -104,6 +109,7 @@ public class HdmiCecLocalDeviceTest { private MyHdmiCecLocalDevice mHdmiLocalDevice; private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; + private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); private TestLooper mTestLooper = new TestLooper(); private static int mDesAddr = -1; private static int mSrcAddr = -1; @@ -139,6 +145,10 @@ public class HdmiCecLocalDeviceTest { } @Override + protected void writeStringSystemProperty(String key, String value) { + } + + @Override void standby() { mStandbyMessageReceived = true; } @@ -149,8 +159,9 @@ public class HdmiCecLocalDeviceTest { } }; mHdmiControlService.setIoLooper(mTestLooper.getLooper()); + mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper( - mHdmiControlService, new FakeNativeWrapper(), mHdmiControlService.getAtomWriter()); + mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiLocalDevice = new MyHdmiCecLocalDevice(mHdmiControlService, DEVICE_TV); mMessageValidator = @@ -161,14 +172,25 @@ public class HdmiCecLocalDeviceTest { } }; mHdmiControlService.setMessageValidator(mMessageValidator); + + mLocalDevices.add(mHdmiLocalDevice); + HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; + hdmiPortInfos[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false); + mNativeWrapper.setPortInfo(hdmiPortInfos); + mNativeWrapper.setPortConnectionStatus(1, true); + mHdmiControlService.initService(); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mNativeWrapper.setPhysicalAddress(0x2000); + mTestLooper.dispatchAll(); } @Test - public void dispatchMessage_desNotValid() { + public void dispatchMessage_logicalAddressDoesNotMatch() { HdmiCecMessage msg = new HdmiCecMessage( ADDR_TV, - ADDR_TV, + ADDR_PLAYBACK_1, Constants.MESSAGE_CEC_VERSION, HdmiCecMessage.EMPTY_PARAM); boolean handleResult = mHdmiLocalDevice.dispatchMessage(msg); @@ -384,4 +406,26 @@ public class HdmiCecLocalDeviceTest { assertThat(mWakeupMessageReceived).isFalse(); assertThat(mStandbyMessageReceived).isTrue(); } + + @Test + public void handleVendorCommand_notHandled() { + HdmiCecMessage vendorCommand = HdmiCecMessageBuilder.buildVendorCommand(ADDR_TV, + ADDR_PLAYBACK_1, new byte[]{0}); + mNativeWrapper.onCecMessage(vendorCommand); + mTestLooper.dispatchAll(); + + HdmiCecMessageBuilder.buildFeatureAbortCommand(ADDR_PLAYBACK_1, ADDR_TV, + vendorCommand.getOpcode(), Constants.ABORT_REFUSED); + } + + @Test + public void handleVendorCommandWithId_notHandled_Cec14() { + HdmiCecMessage vendorCommand = HdmiCecMessageBuilder.buildVendorCommandWithId(ADDR_TV, + ADDR_PLAYBACK_1, 0x1234, new byte[]{0}); + mNativeWrapper.onCecMessage(vendorCommand); + mTestLooper.dispatchAll(); + + HdmiCecMessageBuilder.buildFeatureAbortCommand(ADDR_PLAYBACK_1, ADDR_TV, + vendorCommand.getOpcode(), Constants.ABORT_REFUSED); + } } diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java index be8e569c7a45..99ecb868db6e 100644 --- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java +++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java @@ -41,6 +41,7 @@ import android.provider.Settings; import android.util.Log; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -131,6 +132,7 @@ public class BackgroundRestrictionsTest { } @Test + @FlakyTest public void testPowerWhiteList() throws Exception { scheduleAndAssertJobStarted(); setAppOpsModeAllowed(false); diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java index 23365f70b2f4..972b3bb8b459 100644 --- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java +++ b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java @@ -28,6 +28,7 @@ import static com.android.server.location.timezone.TimeZoneProviderEvent.createU 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.junit.Assert.fail; @@ -88,6 +89,81 @@ public class ControllerImplTest { } @Test + public void initializationFailure_primary() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout() + .plus(testEnvironment.getProviderInitializationTimeoutFuzz()); + + mTestPrimaryLocationTimeZoneProvider.setFailDuringInitialization(true); + + // Initialize. After initialization the providers must be initialized and one should be + // started. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertInitialized(); + mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestSecondaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + @Test + public void initializationFailure_secondary() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout() + .plus(testEnvironment.getProviderInitializationTimeoutFuzz()); + + mTestSecondaryLocationTimeZoneProvider.setFailDuringInitialization(true); + + // Initialize. After initialization the providers must be initialized and one should be + // started. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertInitialized(); + mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); + mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + @Test + public void initializationFailure_both() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + mTestPrimaryLocationTimeZoneProvider.setFailDuringInitialization(true); + mTestSecondaryLocationTimeZoneProvider.setFailDuringInitialization(true); + + // Initialize. After initialization the providers must be initialized and one should be + // started. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertInitialized(); + mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestCallback.assertUncertainSuggestionMadeAndCommit(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + @Test public void initialState_started() { ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); @@ -923,6 +999,74 @@ public class ControllerImplTest { assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } + @Test + public void stateRecording() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. + controllerImpl.initialize(testEnvironment, mTestCallback); + + { + LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests(); + assertNull(state.getLastSuggestion()); + assertTrue(state.getPrimaryProviderStates().isEmpty()); + assertTrue(state.getSecondaryProviderStates().isEmpty()); + } + + // State recording and simulate some provider behavior that will show up in the state + // recording. + controllerImpl.setProviderStateRecordingEnabled(true); + + // Simulate an uncertain event from the primary. This will start the secondary. + mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( + USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + + { + LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests(); + assertNull(state.getLastSuggestion()); + List<LocationTimeZoneProvider.ProviderState> primaryProviderStates = + state.getPrimaryProviderStates(); + assertEquals(1, primaryProviderStates.size()); + assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN, + primaryProviderStates.get(0).stateEnum); + List<LocationTimeZoneProvider.ProviderState> secondaryProviderStates = + state.getSecondaryProviderStates(); + assertEquals(1, secondaryProviderStates.size()); + assertEquals(PROVIDER_STATE_STARTED_INITIALIZING, + secondaryProviderStates.get(0).stateEnum); + } + + // Simulate an uncertain event from the primary. This will start the secondary. + mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + + { + LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests(); + assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(), + state.getLastSuggestion().getZoneIds()); + List<LocationTimeZoneProvider.ProviderState> primaryProviderStates = + state.getPrimaryProviderStates(); + assertEquals(1, primaryProviderStates.size()); + assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN, primaryProviderStates.get(0).stateEnum); + List<LocationTimeZoneProvider.ProviderState> secondaryProviderStates = + state.getSecondaryProviderStates(); + assertEquals(2, secondaryProviderStates.size()); + assertEquals(PROVIDER_STATE_STARTED_CERTAIN, secondaryProviderStates.get(1).stateEnum); + } + + controllerImpl.setProviderStateRecordingEnabled(false); + { + LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests(); + assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(), + state.getLastSuggestion().getZoneIds()); + assertTrue(state.getPrimaryProviderStates().isEmpty()); + assertTrue(state.getSecondaryProviderStates().isEmpty()); + } + } + private static void assertUncertaintyTimeoutSet( LocationTimeZoneProviderController.Environment environment, LocationTimeZoneProviderController controller) { @@ -1028,6 +1172,7 @@ public class ControllerImplTest { /** Used to track historic provider states for tests. */ private final TestState<ProviderState> mTestProviderState = new TestState<>(); + private boolean mFailDuringInitialization; private boolean mInitialized; private boolean mDestroyed; @@ -1038,9 +1183,16 @@ public class ControllerImplTest { super(threadingDomain, providerName); } + public void setFailDuringInitialization(boolean failInitialization) { + mFailDuringInitialization = failInitialization; + } + @Override void onInitialize() { mInitialized = true; + if (mFailDuringInitialization) { + throw new RuntimeException("Simulated initialization failure"); + } } @Override diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java index 49c67ea1b8f1..cb292db50115 100644 --- a/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java @@ -18,6 +18,7 @@ package com.android.server.location.timezone; import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY; import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; @@ -49,6 +50,7 @@ import org.junit.Test; import java.time.Duration; import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; /** @@ -174,6 +176,48 @@ public class LocationTimeZoneProviderTest { assertNotNull(result.getString(TEST_COMMAND_RESULT_ERROR_KEY)); } + @Test + public void stateRecording() { + String providerName = "primary"; + TestLocationTimeZoneProvider provider = + new TestLocationTimeZoneProvider(mTestThreadingDomain, providerName); + provider.setStateChangeRecordingEnabled(true); + + // initialize() + provider.initialize(mProviderListener); + provider.assertLatestRecordedState(PROVIDER_STATE_STOPPED); + + // startUpdates() + ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED; + Duration arbitraryInitializationTimeout = Duration.ofMinutes(5); + Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2); + provider.startUpdates(config, arbitraryInitializationTimeout, + arbitraryInitializationTimeoutFuzz); + provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_INITIALIZING); + + // Simulate a suggestion event being received. + TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder() + .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .setTimeZoneIds(Arrays.asList("Europe/London")) + .build(); + TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion); + provider.simulateProviderEventReceived(event); + provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_CERTAIN); + + // Simulate an uncertain event being received. + event = TimeZoneProviderEvent.createUncertainEvent(); + provider.simulateProviderEventReceived(event); + provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN); + + // stopUpdates() + provider.stopUpdates(); + provider.assertLatestRecordedState(PROVIDER_STATE_STOPPED); + + // destroy() + provider.destroy(); + provider.assertLatestRecordedState(PROVIDER_STATE_DESTROYED); + } + /** A test stand-in for the real {@link LocationTimeZoneProviderController}'s listener. */ private static class TestProviderListener implements ProviderListener { @@ -257,5 +301,11 @@ public class LocationTimeZoneProviderTest { void assertOnDestroyCalled() { assertTrue(mOnDestroyCalled); } + + void assertLatestRecordedState(@ProviderState.ProviderStateEnum int expectedStateEnum) { + List<ProviderState> recordedStates = getRecordedStates(); + assertEquals(expectedStateEnum, + recordedStates.get(recordedStates.size() - 1).stateEnum); + } } } 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 9c8a38219a9c..ac9316e7d908 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 static org.junit.Assert.fail; 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 @@ public class NetworkWatchlistServiceTests { 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/people/data/ConversationInfoTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java index c6823ebfd655..8139310a4c3d 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java @@ -16,11 +16,17 @@ package com.android.server.people.data; +import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY; +import static android.app.people.ConversationStatus.ACTIVITY_GAME; + +import static com.google.common.truth.Truth.assertThat; + 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 android.app.people.ConversationStatus; import android.content.LocusId; import android.content.pm.ShortcutInfo; import android.net.Uri; @@ -41,6 +47,9 @@ public final class ConversationInfoTest { @Test public void testBuild() { + ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build(); + ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build(); + ConversationInfo conversationInfo = new ConversationInfo.Builder() .setShortcutId(SHORTCUT_ID) .setLocusId(LOCUS_ID) @@ -58,6 +67,8 @@ public final class ConversationInfoTest { .setPersonImportant(true) .setPersonBot(true) .setContactStarred(true) + .addOrUpdateStatus(cs) + .addOrUpdateStatus(cs2) .build(); assertEquals(SHORTCUT_ID, conversationInfo.getShortcutId()); @@ -77,6 +88,8 @@ public final class ConversationInfoTest { assertTrue(conversationInfo.isPersonImportant()); assertTrue(conversationInfo.isPersonBot()); assertTrue(conversationInfo.isContactStarred()); + assertThat(conversationInfo.getStatuses()).contains(cs); + assertThat(conversationInfo.getStatuses()).contains(cs2); } @Test @@ -101,10 +114,15 @@ public final class ConversationInfoTest { assertFalse(conversationInfo.isPersonImportant()); assertFalse(conversationInfo.isPersonBot()); assertFalse(conversationInfo.isContactStarred()); + assertThat(conversationInfo.getStatuses()).isNotNull(); + assertThat(conversationInfo.getStatuses()).isEmpty(); } @Test public void testBuildFromAnotherConversationInfo() { + ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build(); + ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build(); + ConversationInfo source = new ConversationInfo.Builder() .setShortcutId(SHORTCUT_ID) .setLocusId(LOCUS_ID) @@ -120,6 +138,8 @@ public final class ConversationInfoTest { .setPersonImportant(true) .setPersonBot(true) .setContactStarred(true) + .addOrUpdateStatus(cs) + .addOrUpdateStatus(cs2) .build(); ConversationInfo destination = new ConversationInfo.Builder(source) @@ -141,5 +161,7 @@ public final class ConversationInfoTest { assertTrue(destination.isPersonImportant()); assertTrue(destination.isPersonBot()); assertFalse(destination.isContactStarred()); + assertThat(destination.getStatuses()).contains(cs); + assertThat(destination.getStatuses()).contains(cs2); } } diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 2471210f6325..be8a99c5ce36 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -16,6 +16,8 @@ package com.android.server.people.data; +import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY; +import static android.app.people.ConversationStatus.ACTIVITY_GAME; import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED; import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; @@ -24,6 +26,8 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.fail; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -50,6 +54,7 @@ import android.app.NotificationManager; import android.app.Person; import android.app.job.JobScheduler; import android.app.people.ConversationChannel; +import android.app.people.ConversationStatus; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.prediction.AppTargetId; @@ -937,6 +942,83 @@ public final class DataManagerTest { } @Test + public void testAddOrUpdateStatus_noCachedShortcut() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build(); + + try { + mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs); + fail("Updated a conversation info that didn't previously exist"); + } catch (IllegalArgumentException e) { + // good + } + } + + @Test + public void testAddOrUpdateStatus() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.addOrUpdateConversationInfo(shortcut); + + ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build(); + mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs); + + assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) + .contains(cs); + + ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build(); + mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs2); + + assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) + .contains(cs); + assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) + .contains(cs2); + } + + @Test + public void testClearStatus() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.addOrUpdateConversationInfo(shortcut); + + ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build(); + ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build(); + mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs); + mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs2); + + mDataManager.clearStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs2.getId()); + + assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) + .contains(cs); + assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) + .doesNotContain(cs2); + } + + @Test + public void testClearStatuses() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.addOrUpdateConversationInfo(shortcut); + + ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build(); + ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build(); + mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs); + mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs2); + + mDataManager.clearStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID); + + assertThat(mDataManager.getStatuses(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)) + .isEmpty(); + } + + @Test public void testNonCachedShortcutNotInRecentList() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index 205548cc8d3c..9a52643b57f2 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -209,7 +209,10 @@ public class AppsFilterTest { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, mMockExecutor); + final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); + watcher.register(); appsFilter.onSystemReady(); + watcher.verifyChangeReported("systemReady"); verify(mFeatureConfigMock).onSystemReady(); } @@ -218,45 +221,60 @@ public class AppsFilterTest { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, mMockExecutor); + final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); + watcher.register(); simulateAddBasicAndroid(appsFilter); + watcher.verifyChangeReported("addBasicAndroid"); appsFilter.onSystemReady(); + watcher.verifyChangeReported("systemReady"); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package", new IntentFilter("TEST_ACTION")), DUMMY_TARGET_APPID); + watcher.verifyChangeReported("add package"); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID); + watcher.verifyChangeReported("add package"); assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, SYSTEM_USER)); + watcher.verifyNoChangeReported("shouldFilterAplication"); } - @Test public void testQueriesProtectedAction_FilterDoesNotMatch() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, mMockExecutor); + final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); + watcher.register(); final Signature frameworkSignature = Mockito.mock(Signature.class); final PackageParser.SigningDetails frameworkSigningDetails = new PackageParser.SigningDetails(new Signature[]{frameworkSignature}, 1); final ParsingPackage android = pkg("android"); + watcher.verifyNoChangeReported("prepare"); android.addProtectedBroadcast("TEST_ACTION"); simulateAddPackage(appsFilter, android, 1000, b -> b.setSigningDetails(frameworkSigningDetails)); + watcher.verifyChangeReported("addPackage"); appsFilter.onSystemReady(); + watcher.verifyChangeReported("systemReady"); final int activityUid = DUMMY_TARGET_APPID; PackageSetting targetActivity = simulateAddPackage(appsFilter, pkg("com.target.activity", new IntentFilter("TEST_ACTION")), activityUid); + watcher.verifyChangeReported("addPackage"); final int receiverUid = DUMMY_TARGET_APPID + 1; PackageSetting targetReceiver = simulateAddPackage(appsFilter, pkgWithReceiver("com.target.receiver", new IntentFilter("TEST_ACTION")), receiverUid); + watcher.verifyChangeReported("addPackage"); final int callingUid = DUMMY_CALLING_APPID; PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.calling.action", new Intent("TEST_ACTION")), callingUid); + watcher.verifyChangeReported("addPackage"); final int wildcardUid = DUMMY_CALLING_APPID + 1; PackageSetting callingWildCard = simulateAddPackage(appsFilter, pkg("com.calling.wildcard", new Intent("*")), wildcardUid); + watcher.verifyChangeReported("addPackage"); assertFalse(appsFilter.shouldFilterApplication(callingUid, calling, targetActivity, SYSTEM_USER)); @@ -267,6 +285,7 @@ public class AppsFilterTest { wildcardUid, callingWildCard, targetActivity, SYSTEM_USER)); assertTrue(appsFilter.shouldFilterApplication( wildcardUid, callingWildCard, targetReceiver, SYSTEM_USER)); + watcher.verifyNoChangeReported("shouldFilterApplication"); } @Test @@ -274,17 +293,24 @@ public class AppsFilterTest { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, mMockExecutor); + final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); + watcher.register(); simulateAddBasicAndroid(appsFilter); + watcher.verifyChangeReported("addPackage"); appsFilter.onSystemReady(); + watcher.verifyChangeReported("systemReady"); PackageSetting target = simulateAddPackage(appsFilter, pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID); + watcher.verifyChangeReported("addPackage"); PackageSetting calling = simulateAddPackage(appsFilter, pkgQueriesProvider("com.some.other.package", "com.some.authority"), DUMMY_CALLING_APPID); + watcher.verifyChangeReported("addPackage"); assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, SYSTEM_USER)); + watcher.verifyNoChangeReported("shouldFilterApplication"); } @Test @@ -292,17 +318,24 @@ public class AppsFilterTest { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, mMockExecutor); + final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); + watcher.register(); simulateAddBasicAndroid(appsFilter); + watcher.verifyChangeReported("addPackage"); appsFilter.onSystemReady(); + watcher.verifyChangeReported("systemReady"); PackageSetting target = simulateAddPackage(appsFilter, pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID); + watcher.verifyChangeReported("addPackage"); PackageSetting calling = simulateAddPackage(appsFilter, pkgQueriesProvider("com.some.other.package", "com.some.other.authority"), DUMMY_CALLING_APPID); + watcher.verifyChangeReported("addPackage"); assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, SYSTEM_USER)); + watcher.verifyNoChangeReported("shouldFilterApplication"); } @Test @@ -779,16 +812,23 @@ public class AppsFilterTest { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, mMockExecutor); + final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); + watcher.register(); simulateAddBasicAndroid(appsFilter); + watcher.verifyChangeReported("addBasicAndroid"); appsFilter.onSystemReady(); + watcher.verifyChangeReported("systemReady"); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), DUMMY_TARGET_APPID); + watcher.verifyChangeReported("add package"); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_APPID, withInstallSource(null, target.name, null, null, false)); + watcher.verifyChangeReported("add package"); assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, SYSTEM_USER)); + watcher.verifyNoChangeReported("shouldFilterAplication"); } @Test @@ -796,16 +836,23 @@ public class AppsFilterTest { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, mMockExecutor); + final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); + watcher.register(); simulateAddBasicAndroid(appsFilter); + watcher.verifyChangeReported("addBasicAndroid"); appsFilter.onSystemReady(); + watcher.verifyChangeReported("systemReady"); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), DUMMY_TARGET_APPID); + watcher.verifyChangeReported("add package"); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_APPID, withInstallSource(null, null, target.name, null, false)); + watcher.verifyChangeReported("add package"); assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, SYSTEM_USER)); + watcher.verifyNoChangeReported("shouldFilterAplication"); } @Test @@ -813,15 +860,20 @@ public class AppsFilterTest { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, mMockExecutor); + final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); + watcher.register(); simulateAddBasicAndroid(appsFilter); + watcher.verifyChangeReported("addBasicAndroid"); appsFilter.onSystemReady(); - + watcher.verifyChangeReported("systemReady"); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), DUMMY_TARGET_APPID); + watcher.verifyChangeReported("add package"); PackageSetting instrumentation = simulateAddPackage(appsFilter, pkgWithInstrumentation("com.some.other.package", "com.some.package"), DUMMY_CALLING_APPID); + watcher.verifyChangeReported("add package"); assertFalse( appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target, @@ -829,6 +881,7 @@ public class AppsFilterTest { assertFalse( appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, target, instrumentation, SYSTEM_USER)); + watcher.verifyNoChangeReported("shouldFilterAplication"); } @Test @@ -836,8 +889,12 @@ public class AppsFilterTest { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, mMockExecutor); + final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); + watcher.register(); simulateAddBasicAndroid(appsFilter); + watcher.verifyChangeReported("addBasicAndroid"); appsFilter.onSystemReady(); + watcher.verifyChangeReported("systemReady"); final int systemAppId = Process.FIRST_APPLICATION_UID - 1; final int seesNothingAppId = Process.FIRST_APPLICATION_UID; @@ -845,25 +902,34 @@ public class AppsFilterTest { final int queriesProviderAppId = Process.FIRST_APPLICATION_UID + 2; PackageSetting system = simulateAddPackage(appsFilter, pkg("some.system.pkg"), systemAppId); + watcher.verifyChangeReported("add package"); PackageSetting seesNothing = simulateAddPackage(appsFilter, pkg("com.some.package"), seesNothingAppId); + watcher.verifyChangeReported("add package"); PackageSetting hasProvider = simulateAddPackage(appsFilter, pkgWithProvider("com.some.other.package", "com.some.authority"), hasProviderAppId); + watcher.verifyChangeReported("add package"); PackageSetting queriesProvider = simulateAddPackage(appsFilter, pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"), queriesProviderAppId); + watcher.verifyChangeReported("add package"); final SparseArray<int[]> systemFilter = appsFilter.getVisibilityAllowList(system, USER_ARRAY, mExisting); + watcher.verifyNoChangeReported("getVisibility"); assertThat(toList(systemFilter.get(SYSTEM_USER)), contains(seesNothingAppId, hasProviderAppId, queriesProviderAppId)); + watcher.verifyNoChangeReported("getVisibility"); final SparseArray<int[]> seesNothingFilter = appsFilter.getVisibilityAllowList(seesNothing, USER_ARRAY, mExisting); + watcher.verifyNoChangeReported("getVisibility"); assertThat(toList(seesNothingFilter.get(SYSTEM_USER)), contains(seesNothingAppId)); + watcher.verifyNoChangeReported("getVisibility"); assertThat(toList(seesNothingFilter.get(SECONDARY_USER)), contains(seesNothingAppId)); + watcher.verifyNoChangeReported("getVisibility"); final SparseArray<int[]> hasProviderFilter = appsFilter.getVisibilityAllowList(hasProvider, USER_ARRAY, mExisting); @@ -872,17 +938,22 @@ public class AppsFilterTest { SparseArray<int[]> queriesProviderFilter = appsFilter.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting); + watcher.verifyNoChangeReported("getVisibility"); assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)), contains(queriesProviderAppId)); + watcher.verifyNoChangeReported("getVisibility"); // provider read appsFilter.grantImplicitAccess(hasProviderAppId, queriesProviderAppId); + watcher.verifyChangeReported("grantImplicitAccess"); // ensure implicit access is included in the filter queriesProviderFilter = appsFilter.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting); + watcher.verifyNoChangeReported("getVisibility"); assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)), contains(hasProviderAppId, queriesProviderAppId)); + watcher.verifyNoChangeReported("getVisibility"); } @Test 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 d54a40e58af1..c010e1995446 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 android.util.SparseArray; 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.Collections; 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 @@ public class PackageManagerServiceTest { } } + @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/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index 282047afaa51..333ec9295b93 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -1215,7 +1215,7 @@ public class PackageManagerSettingsTests { private void verifyKeySetMetaData(Settings settings) throws ReflectiveOperationException, IllegalAccessException { ArrayMap<String, PackageSetting> packages = - settings.mPackages.untrackedMap(); + settings.mPackages.untrackedStorage(); KeySetManagerService ksms = settings.mKeySetManagerService; /* verify keyset and public key ref counts */ diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java index 08d4caacd777..5f654406812f 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -32,8 +32,13 @@ import androidx.test.InstrumentationRegistry; import com.android.server.SystemService; import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper; +import com.android.server.powerstats.nano.PowerEntityInfoProto; import com.android.server.powerstats.nano.PowerStatsServiceMeterProto; import com.android.server.powerstats.nano.PowerStatsServiceModelProto; +import com.android.server.powerstats.nano.PowerStatsServiceResidencyProto; +import com.android.server.powerstats.nano.StateInfoProto; +import com.android.server.powerstats.nano.StateResidencyProto; +import com.android.server.powerstats.nano.StateResidencyResultProto; import org.junit.Before; import org.junit.Test; @@ -58,6 +63,7 @@ public class PowerStatsServiceTest { private static final String DATA_STORAGE_SUBDIR = "powerstatstest"; private static final String METER_FILENAME = "metertest"; private static final String MODEL_FILENAME = "modeltest"; + private static final String RESIDENCY_FILENAME = "residencytest"; private static final String PROTO_OUTPUT_FILENAME = "powerstats.proto"; private static final String CHANNEL_NAME = "channelname"; private static final String POWER_ENTITY_NAME = "powerentityinfo"; @@ -72,6 +78,7 @@ public class PowerStatsServiceTest { private PowerStatsService mService; private File mDataStorageDir; private TimerTrigger mTimerTrigger; + private BatteryTrigger mBatteryTrigger; private PowerStatsLogger mPowerStatsLogger; private final PowerStatsService.Injector mInjector = new PowerStatsService.Injector() { @@ -99,22 +106,29 @@ public class PowerStatsServiceTest { } @Override + String createResidencyFilename() { + return RESIDENCY_FILENAME; + } + + @Override IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() { return new TestPowerStatsHALWrapper(); } @Override PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath, - String meterFilename, String modelFilename, + String meterFilename, String modelFilename, String residencyFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, meterFilename, - modelFilename, powerStatsHALWrapper); + modelFilename, residencyFilename, powerStatsHALWrapper); return mPowerStatsLogger; } @Override BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) { - return new BatteryTrigger(context, powerStatsLogger, false /* trigger enabled */); + mBatteryTrigger = new BatteryTrigger(context, powerStatsLogger, + false /* trigger enabled */); + return mBatteryTrigger; } @Override @@ -137,7 +151,7 @@ public class PowerStatsServiceTest { for (int j = 0; j < powerEntityInfoList[i].states.length; j++) { powerEntityInfoList[i].states[j] = new StateInfo(); powerEntityInfoList[i].states[j].stateId = j; - powerEntityInfoList[i].states[j].stateName = new String(STATE_NAME + i); + powerEntityInfoList[i].states[j].stateName = new String(STATE_NAME + j); } } return powerEntityInfoList; @@ -154,6 +168,7 @@ public class PowerStatsServiceTest { new StateResidency[STATE_RESIDENCY_COUNT]; for (int j = 0; j < stateResidencyResultList[i].stateResidencyData.length; j++) { stateResidencyResultList[i].stateResidencyData[j] = new StateResidency(); + stateResidencyResultList[i].stateResidencyData[j].stateId = j; stateResidencyResultList[i].stateResidencyData[j].totalTimeInStateMs = j; stateResidencyResultList[i].stateResidencyData[j].totalStateEntryCount = j; stateResidencyResultList[i].stateResidencyData[j].lastEntryTimestampMs = j; @@ -225,7 +240,7 @@ public class PowerStatsServiceTest { mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); // Write data to on-device storage. - mTimerTrigger.logPowerStatsData(); + mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_TIMER); // The above call puts a message on a handler. Wait for // it to be processed. @@ -266,7 +281,7 @@ public class PowerStatsServiceTest { mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); // Write data to on-device storage. - mTimerTrigger.logPowerStatsData(); + mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_TIMER); // The above call puts a message on a handler. Wait for // it to be processed. @@ -301,6 +316,61 @@ public class PowerStatsServiceTest { } @Test + public void testWrittenResidencyDataMatchesReadIncidentReportData() + throws InterruptedException, IOException { + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + // Write data to on-device storage. + mBatteryTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP); + + // The above call puts a message on a handler. Wait for + // it to be processed. + Thread.sleep(100); + + // Write on-device storage to an incident report. + File incidentReport = new File(mDataStorageDir, PROTO_OUTPUT_FILENAME); + FileOutputStream fos = new FileOutputStream(incidentReport); + mPowerStatsLogger.writeResidencyDataToFile(fos.getFD()); + + // Read the incident report in to a byte array. + FileInputStream fis = new FileInputStream(incidentReport); + byte[] fileContent = new byte[(int) incidentReport.length()]; + fis.read(fileContent); + + // Parse the incident data into a PowerStatsServiceResidencyProto object. + PowerStatsServiceResidencyProto pssProto = + PowerStatsServiceResidencyProto.parseFrom(fileContent); + + // Validate the powerEntityInfo array matches what was written to on-device storage. + assertTrue(pssProto.powerEntityInfo.length == POWER_ENTITY_COUNT); + for (int i = 0; i < pssProto.powerEntityInfo.length; i++) { + PowerEntityInfoProto powerEntityInfo = pssProto.powerEntityInfo[i]; + assertTrue(powerEntityInfo.powerEntityId == i); + assertTrue(powerEntityInfo.powerEntityName.equals(POWER_ENTITY_NAME + i)); + for (int j = 0; j < powerEntityInfo.states.length; j++) { + StateInfoProto stateInfo = powerEntityInfo.states[j]; + assertTrue(stateInfo.stateId == j); + assertTrue(stateInfo.stateName.equals(STATE_NAME + j)); + } + } + + // Validate the stateResidencyResult array matches what was written to on-device storage. + assertTrue(pssProto.stateResidencyResult.length == POWER_ENTITY_COUNT); + for (int i = 0; i < pssProto.stateResidencyResult.length; i++) { + StateResidencyResultProto stateResidencyResult = pssProto.stateResidencyResult[i]; + assertTrue(stateResidencyResult.powerEntityId == i); + assertTrue(stateResidencyResult.stateResidencyData.length == STATE_RESIDENCY_COUNT); + for (int j = 0; j < stateResidencyResult.stateResidencyData.length; j++) { + StateResidencyProto stateResidency = stateResidencyResult.stateResidencyData[j]; + assertTrue(stateResidency.stateId == j); + assertTrue(stateResidency.totalTimeInStateMs == j); + assertTrue(stateResidency.totalStateEntryCount == j); + assertTrue(stateResidency.lastEntryTimestampMs == j); + } + } + } + + @Test public void testCorruptOnDeviceMeterStorage() throws IOException { mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); @@ -384,6 +454,55 @@ public class PowerStatsServiceTest { } @Test + public void testCorruptOnDeviceResidencyStorage() throws IOException { + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + // Generate random array of bytes to emulate corrupt data. + Random rd = new Random(); + byte[] bytes = new byte[100]; + rd.nextBytes(bytes); + + // Store corrupt data in on-device storage. Add fake timestamp to filename + // to match format expected by FileRotator. + File onDeviceStorageFile = new File(mDataStorageDir, RESIDENCY_FILENAME + ".1234-2234"); + FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Write on-device storage to an incident report. + File incidentReport = new File(mDataStorageDir, PROTO_OUTPUT_FILENAME); + FileOutputStream incidentReportFos = new FileOutputStream(incidentReport); + mPowerStatsLogger.writeResidencyDataToFile(incidentReportFos.getFD()); + + // Read the incident report in to a byte array. + FileInputStream fis = new FileInputStream(incidentReport); + byte[] fileContent = new byte[(int) incidentReport.length()]; + fis.read(fileContent); + + // Parse the incident data into a PowerStatsServiceResidencyProto object. + PowerStatsServiceResidencyProto pssProto = + PowerStatsServiceResidencyProto.parseFrom(fileContent); + + // Valid powerEntityInfo data is written to the incident report in the call to + // mPowerStatsLogger.writeResidencyDataToFile(). + assertTrue(pssProto.powerEntityInfo.length == POWER_ENTITY_COUNT); + for (int i = 0; i < pssProto.powerEntityInfo.length; i++) { + PowerEntityInfoProto powerEntityInfo = pssProto.powerEntityInfo[i]; + assertTrue(powerEntityInfo.powerEntityId == i); + assertTrue(powerEntityInfo.powerEntityName.equals(POWER_ENTITY_NAME + i)); + for (int j = 0; j < powerEntityInfo.states.length; j++) { + StateInfoProto stateInfo = powerEntityInfo.states[j]; + assertTrue(stateInfo.stateId == j); + assertTrue(stateInfo.stateName.equals(STATE_NAME + j)); + } + } + + // No stateResidencyResults should be written to the incident report since it + // is all corrupt (random bytes generated above). + assertTrue(pssProto.stateResidencyResult.length == 0); + } + + @Test public void testNotEnoughBytesAfterMeterLengthField() throws IOException { mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); @@ -467,4 +586,54 @@ public class PowerStatsServiceTest { // input buffer had only length and no data. assertTrue(pssProto.energyConsumerResult.length == 0); } + + @Test + public void testNotEnoughBytesAfterResidencyLengthField() throws IOException { + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + // Create corrupt data. + // Length field is correct, but there is no data following the length. + ByteArrayOutputStream data = new ByteArrayOutputStream(); + data.write(ByteBuffer.allocate(4).putInt(50).array()); + byte[] test = data.toByteArray(); + + // Store corrupt data in on-device storage. Add fake timestamp to filename + // to match format expected by FileRotator. + File onDeviceStorageFile = new File(mDataStorageDir, RESIDENCY_FILENAME + ".1234-2234"); + FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(data.toByteArray()); + onDeviceStorageFos.close(); + + // Write on-device storage to an incident report. + File incidentReport = new File(mDataStorageDir, PROTO_OUTPUT_FILENAME); + FileOutputStream incidentReportFos = new FileOutputStream(incidentReport); + mPowerStatsLogger.writeResidencyDataToFile(incidentReportFos.getFD()); + + // Read the incident report in to a byte array. + FileInputStream fis = new FileInputStream(incidentReport); + byte[] fileContent = new byte[(int) incidentReport.length()]; + fis.read(fileContent); + + // Parse the incident data into a PowerStatsServiceResidencyProto object. + PowerStatsServiceResidencyProto pssProto = + PowerStatsServiceResidencyProto.parseFrom(fileContent); + + // Valid powerEntityInfo data is written to the incident report in the call to + // mPowerStatsLogger.writeResidencyDataToFile(). + assertTrue(pssProto.powerEntityInfo.length == POWER_ENTITY_COUNT); + for (int i = 0; i < pssProto.powerEntityInfo.length; i++) { + PowerEntityInfoProto powerEntityInfo = pssProto.powerEntityInfo[i]; + assertTrue(powerEntityInfo.powerEntityId == i); + assertTrue(powerEntityInfo.powerEntityName.equals(POWER_ENTITY_NAME + i)); + for (int j = 0; j < powerEntityInfo.states.length; j++) { + StateInfoProto stateInfo = powerEntityInfo.states[j]; + assertTrue(stateInfo.stateId == j); + assertTrue(stateInfo.stateName.equals(STATE_NAME + j)); + } + } + + // No stateResidencyResults should be written to the incident report since the + // input buffer had only length and no data. + assertTrue(pssProto.stateResidencyResult.length == 0); + } } diff --git a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java index 9bea9d4cedbd..7c65dc03a57e 100644 --- a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java @@ -20,12 +20,20 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; + import androidx.test.filters.SmallTest; import org.junit.After; import org.junit.Before; import org.junit.Test; +import java.util.ArrayList; + /** * Test class for {@link Watcher}, {@link Watchable}, {@link WatchableImpl}, * {@link WatchedArrayMap}, {@link WatchedSparseArray}, and @@ -40,7 +48,7 @@ public class WatcherTest { // A counter to generate unique IDs for Leaf elements. private int mLeafId = 0; - // Useful indices used int the tests. + // Useful indices used in the tests. private static final int INDEX_A = 1; private static final int INDEX_B = 2; private static final int INDEX_C = 3; @@ -171,6 +179,7 @@ public class WatcherTest { @Test public void testWatchedArrayMap() { + final String name = "WatchedArrayMap"; WatchableTester tester; // Create a few leaves @@ -183,7 +192,7 @@ public class WatcherTest { WatchedArrayMap<Integer, Leaf> array = new WatchedArrayMap<>(); array.put(INDEX_A, leafA); array.put(INDEX_B, leafB); - tester = new WatchableTester(array, "WatchedArrayMap"); + tester = new WatchableTester(array, name); tester.verify(0, "Initial array - no registration"); leafA.tick(); tester.verify(0, "Updates with no registration"); @@ -231,20 +240,20 @@ public class WatcherTest { final WatchedArrayMap<Integer, Leaf> arraySnap = array.snapshot(); tester.verify(14, "Generate snapshot (no changes)"); // Verify that the snapshot is a proper copy of the source. - assertEquals("WatchedArrayMap snap same size", + assertEquals(name + " snap same size", array.size(), arraySnap.size()); for (int i = 0; i < array.size(); i++) { for (int j = 0; j < arraySnap.size(); j++) { - assertTrue("WatchedArrayMap elements differ", + assertTrue(name + " elements differ", array.valueAt(i) != arraySnap.valueAt(j)); } - assertTrue("WatchedArrayMap element copy", + assertTrue(name + " element copy", array.valueAt(i).equals(arraySnap.valueAt(i))); } leafD.tick(); tester.verify(15, "Tick after snapshot"); // Verify that the snapshot is sealed - verifySealed("WatchedArrayMap", ()->arraySnap.put(INDEX_A, leafA)); + verifySealed(name, ()->arraySnap.put(INDEX_A, leafA)); } // Recreate the snapshot since the test corrupted it. { @@ -253,10 +262,235 @@ public class WatcherTest { final Leaf arraySnapElement = arraySnap.valueAt(0); verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick()); } + // Verify copy-in/out + { + final String msg = name + " copy-in/out failed"; + ArrayMap<Integer, Leaf> base = new ArrayMap<>(); + array.copyTo(base); + WatchedArrayMap<Integer, Leaf> copy = new WatchedArrayMap<>(); + copy.copyFrom(base); + if (!array.equals(copy)) { + fail(msg); + } + } + } + + @Test + public void testWatchedArraySet() { + final String name = "WatchedArraySet"; + WatchableTester tester; + + // Create a few leaves + Leaf leafA = new Leaf(); + Leaf leafB = new Leaf(); + Leaf leafC = new Leaf(); + Leaf leafD = new Leaf(); + + // Test WatchedArraySet + WatchedArraySet<Leaf> array = new WatchedArraySet<>(); + array.add(leafA); + array.add(leafB); + tester = new WatchableTester(array, name); + tester.verify(0, "Initial array - no registration"); + leafA.tick(); + tester.verify(0, "Updates with no registration"); + tester.register(); + tester.verify(0, "Updates with no registration"); + leafA.tick(); + tester.verify(1, "Updates with registration"); + leafB.tick(); + tester.verify(2, "Updates with registration"); + array.remove(leafB); + tester.verify(3, "Removed b"); + leafB.tick(); + tester.verify(3, "Updates with b not watched"); + array.add(leafB); + array.add(leafB); + tester.verify(5, "Added b once"); + leafB.tick(); + tester.verify(6, "Changed b - single notification"); + array.remove(leafB); + tester.verify(7, "Removed b"); + leafB.tick(); + tester.verify(7, "Changed b - not watched"); + array.remove(leafB); + tester.verify(7, "Removed non-existent b"); + array.clear(); + tester.verify(8, "Cleared array"); + leafA.tick(); + tester.verify(8, "Change to a not in array"); + + // Special methods + array.add(leafA); + array.add(leafB); + array.add(leafC); + tester.verify(11, "Added a, b, c"); + leafC.tick(); + tester.verify(12, "Ticked c"); + array.removeAt(array.indexOf(leafC)); + tester.verify(13, "Removed c"); + leafC.tick(); + tester.verify(13, "Ticked c, not registered"); + array.append(leafC); + tester.verify(14, "Append c"); + leafC.tick(); + leafD.tick(); + tester.verify(15, "Ticked d and c"); + assertEquals("Verify three elements", 3, array.size()); + + // Snapshot + { + final WatchedArraySet<Leaf> arraySnap = array.snapshot(); + tester.verify(15, "Generate snapshot (no changes)"); + // Verify that the snapshot is a proper copy of the source. + assertEquals(name + " snap same size", + array.size(), arraySnap.size()); + for (int i = 0; i < array.size(); i++) { + for (int j = 0; j < arraySnap.size(); j++) { + assertTrue(name + " elements differ", + array.valueAt(i) != arraySnap.valueAt(j)); + } + } + leafC.tick(); + tester.verify(16, "Tick after snapshot"); + // Verify that the array snapshot is sealed + verifySealed(name, ()->arraySnap.add(leafB)); + } + // Recreate the snapshot since the test corrupted it. + { + final WatchedArraySet<Leaf> arraySnap = array.snapshot(); + // Verify that elements are also snapshots + final Leaf arraySnapElement = arraySnap.valueAt(0); + verifySealed(name + " snap element", ()->arraySnapElement.tick()); + } + // Verify copy-in/out + { + final String msg = name + " copy-in/out"; + ArraySet<Leaf> base = new ArraySet<>(); + array.copyTo(base); + WatchedArraySet<Leaf> copy = new WatchedArraySet<>(); + copy.copyFrom(base); + if (!array.equals(copy)) { + fail(msg); + } + } + } + + @Test + public void testWatchedArrayList() { + final String name = "WatchedArrayList"; + WatchableTester tester; + + // Create a few leaves + Leaf leafA = new Leaf(); + Leaf leafB = new Leaf(); + Leaf leafC = new Leaf(); + Leaf leafD = new Leaf(); + + // Redefine the indices used in the tests to be zero-based + final int indexA = 0; + final int indexB = 1; + final int indexC = 2; + final int indexD = 3; + + // Test WatchedArrayList + WatchedArrayList<Leaf> array = new WatchedArrayList<>(); + // A spacer that takes up index 0 (and is not Watchable). + array.add(indexA, leafA); + array.add(indexB, leafB); + tester = new WatchableTester(array, name); + tester.verify(0, "Initial array - no registration"); + leafA.tick(); + tester.verify(0, "Updates with no registration"); + tester.register(); + tester.verify(0, "Updates with no registration"); + leafA.tick(); + tester.verify(1, "Updates with registration"); + leafB.tick(); + tester.verify(2, "Updates with registration"); + array.remove(indexB); + tester.verify(3, "Removed b"); + leafB.tick(); + tester.verify(3, "Updates with b not watched"); + array.add(indexB, leafB); + array.add(indexC, leafB); + tester.verify(5, "Added b twice"); + leafB.tick(); + tester.verify(6, "Changed b - single notification"); + array.remove(indexC); + tester.verify(7, "Removed first b"); + leafB.tick(); + tester.verify(8, "Changed b - single notification"); + array.remove(indexB); + tester.verify(9, "Removed second b"); + leafB.tick(); + tester.verify(9, "Updated leafB - no change"); + array.clear(); + tester.verify(10, "Cleared array"); + leafB.tick(); + tester.verify(10, "Change to b not in array"); + + // Special methods + array.add(indexA, leafA); + array.add(indexB, leafB); + array.add(indexC, leafC); + tester.verify(13, "Added c"); + leafC.tick(); + tester.verify(14, "Ticked c"); + array.set(array.indexOf(leafC), leafD); + tester.verify(15, "Replaced c with d"); + leafC.tick(); + leafD.tick(); + tester.verify(16, "Ticked d and c (c not registered)"); + array.add(leafC); + tester.verify(17, "Append c"); + leafC.tick(); + leafD.tick(); + tester.verify(19, "Ticked d and c"); + + // Snapshot + { + final WatchedArrayList<Leaf> arraySnap = array.snapshot(); + tester.verify(19, "Generate snapshot (no changes)"); + // Verify that the snapshot is a proper copy of the source. + assertEquals(name + " snap same size", + array.size(), arraySnap.size()); + for (int i = 0; i < array.size(); i++) { + for (int j = 0; j < arraySnap.size(); j++) { + assertTrue(name + " elements differ", + array.get(i) != arraySnap.get(j)); + } + assertTrue(name + " element copy", + array.get(i).equals(arraySnap.get(i))); + } + leafD.tick(); + tester.verify(20, "Tick after snapshot"); + // Verify that the array snapshot is sealed + verifySealed(name, ()->arraySnap.add(indexA, leafB)); + } + // Recreate the snapshot since the test corrupted it. + { + final WatchedArrayList<Leaf> arraySnap = array.snapshot(); + // Verify that elements are also snapshots + final Leaf arraySnapElement = arraySnap.get(0); + verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick()); + } + // Verify copy-in/out + { + final String msg = name + " copy-in/out"; + ArrayList<Leaf> base = new ArrayList<>(); + array.copyTo(base); + WatchedArrayList<Leaf> copy = new WatchedArrayList<>(); + copy.copyFrom(base); + if (!array.equals(copy)) { + fail(msg); + } + } } @Test public void testWatchedSparseArray() { + final String name = "WatchedSparseArray"; WatchableTester tester; // Create a few leaves @@ -269,7 +503,7 @@ public class WatcherTest { WatchedSparseArray<Leaf> array = new WatchedSparseArray<>(); array.put(INDEX_A, leafA); array.put(INDEX_B, leafB); - tester = new WatchableTester(array, "WatchedSparseArray"); + tester = new WatchableTester(array, name); tester.verify(0, "Initial array - no registration"); leafA.tick(); tester.verify(0, "Updates with no registration"); @@ -338,20 +572,20 @@ public class WatcherTest { final WatchedSparseArray<Leaf> arraySnap = array.snapshot(); tester.verify(22, "Generate snapshot (no changes)"); // Verify that the snapshot is a proper copy of the source. - assertEquals("WatchedSparseArray snap same size", + assertEquals(name + " snap same size", array.size(), arraySnap.size()); for (int i = 0; i < array.size(); i++) { for (int j = 0; j < arraySnap.size(); j++) { - assertTrue("WatchedSparseArray elements differ", + assertTrue(name + " elements differ", array.valueAt(i) != arraySnap.valueAt(j)); } - assertTrue("WatchedArrayMap element copy", + assertTrue(name + " element copy", array.valueAt(i).equals(arraySnap.valueAt(i))); } leafD.tick(); tester.verify(23, "Tick after snapshot"); // Verify that the array snapshot is sealed - verifySealed("WatchedSparseArray", ()->arraySnap.put(INDEX_A, leafB)); + verifySealed(name, ()->arraySnap.put(INDEX_A, leafB)); } // Recreate the snapshot since the test corrupted it. { @@ -360,15 +594,30 @@ public class WatcherTest { final Leaf arraySnapElement = arraySnap.valueAt(0); verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick()); } + // Verify copy-in/out + { + final String msg = name + " copy-in/out"; + SparseArray<Leaf> base = new SparseArray<>(); + array.copyTo(base); + WatchedSparseArray<Leaf> copy = new WatchedSparseArray<>(); + copy.copyFrom(base); + final int end = array.size(); + assertTrue(msg + " size mismatch " + end + " " + copy.size(), end == copy.size()); + for (int i = 0; i < end; i++) { + final int key = array.keyAt(i); + assertTrue(msg, array.get(i) == copy.get(i)); + } + } } @Test public void testWatchedSparseBooleanArray() { + final String name = "WatchedSparseBooleanArray"; WatchableTester tester; // Test WatchedSparseBooleanArray WatchedSparseBooleanArray array = new WatchedSparseBooleanArray(); - tester = new WatchableTester(array, "WatchedSparseBooleanArray"); + tester = new WatchableTester(array, name); tester.verify(0, "Initial array - no registration"); array.put(INDEX_A, true); tester.verify(0, "Updates with no registration"); @@ -376,14 +625,10 @@ public class WatcherTest { tester.verify(0, "Updates with no registration"); array.put(INDEX_B, true); tester.verify(1, "Updates with registration"); - array.put(INDEX_B, true); - tester.verify(1, "Null update"); array.put(INDEX_B, false); array.put(INDEX_C, true); tester.verify(3, "Updates with registration"); // Special methods - array.put(INDEX_C, true); - tester.verify(3, "Added true, no change"); array.setValueAt(array.indexOfKey(INDEX_C), false); tester.verify(4, "Replaced true with false"); array.append(INDEX_D, true); @@ -403,7 +648,77 @@ public class WatcherTest { array.put(INDEX_D, false); tester.verify(6, "Tick after snapshot"); // Verify that the array is sealed - verifySealed("WatchedSparseBooleanArray", ()->arraySnap.put(INDEX_D, false)); + verifySealed(name, ()->arraySnap.put(INDEX_D, false)); + } + // Verify copy-in/out + { + final String msg = name + " copy-in/out"; + SparseBooleanArray base = new SparseBooleanArray(); + array.copyTo(base); + WatchedSparseBooleanArray copy = new WatchedSparseBooleanArray(); + copy.copyFrom(base); + final int end = array.size(); + assertTrue(msg + " size mismatch/2 " + end + " " + copy.size(), end == copy.size()); + for (int i = 0; i < end; i++) { + final int key = array.keyAt(i); + assertTrue(msg + " element", array.get(i) == copy.get(i)); + } + } + } + + @Test + public void testWatchedSparseIntArray() { + final String name = "WatchedSparseIntArray"; + WatchableTester tester; + + // Test WatchedSparseIntArray + WatchedSparseIntArray array = new WatchedSparseIntArray(); + tester = new WatchableTester(array, name); + tester.verify(0, "Initial array - no registration"); + array.put(INDEX_A, 1); + tester.verify(0, "Updates with no registration"); + tester.register(); + tester.verify(0, "Updates with no registration"); + array.put(INDEX_B, 2); + tester.verify(1, "Updates with registration"); + array.put(INDEX_B, 4); + array.put(INDEX_C, 5); + tester.verify(3, "Updates with registration"); + // Special methods + array.setValueAt(array.indexOfKey(INDEX_C), 7); + tester.verify(4, "Replaced 6 with 7"); + array.append(INDEX_D, 8); + tester.verify(5, "Append 8"); + + // Snapshot + { + WatchedSparseIntArray arraySnap = array.snapshot(); + tester.verify(5, "Generate snapshot"); + // Verify that the snapshot is a proper copy of the source. + assertEquals("WatchedSparseIntArray snap same size", + array.size(), arraySnap.size()); + for (int i = 0; i < array.size(); i++) { + assertEquals(name + " element copy", + array.valueAt(i), arraySnap.valueAt(i)); + } + array.put(INDEX_D, 9); + tester.verify(6, "Tick after snapshot"); + // Verify that the array is sealed + verifySealed(name, ()->arraySnap.put(INDEX_D, 10)); + } + // Verify copy-in/out + { + final String msg = name + " copy-in/out"; + SparseIntArray base = new SparseIntArray(); + array.copyTo(base); + WatchedSparseIntArray copy = new WatchedSparseIntArray(); + copy.copyFrom(base); + final int end = array.size(); + assertTrue(msg + " size mismatch " + end + " " + copy.size(), end == copy.size()); + for (int i = 0; i < end; i++) { + final int key = array.keyAt(i); + assertTrue(msg, array.get(i) == copy.get(i)); + } } } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java index a093e0d4a740..afcf08ef1562 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -80,7 +80,7 @@ public class NotificationListenersTest extends UiServiceTestCase { @Test public void testReadExtraTag() throws Exception { - String xml = "<requested_listeners>" + String xml = "<req_listeners>" + "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">" + "<allowed types=\"7\" />" + "<disallowed pkgs=\"\" />" @@ -89,13 +89,13 @@ public class NotificationListenersTest extends UiServiceTestCase { + "<allowed types=\"4\" />" + "<disallowed pkgs=\"something\" />" + "</listener>" - + "</requested_listeners>"; + + "</req_listeners>"; TypedXmlPullParser parser = Xml.newFastPullParser(); parser.setInput(new BufferedInputStream( new ByteArrayInputStream(xml.getBytes())), null); parser.nextTag(); - mListeners.readExtraTag("requested_listeners", parser); + mListeners.readExtraTag("req_listeners", parser); validateListenersFromXml(); } @@ -120,7 +120,7 @@ public class NotificationListenersTest extends UiServiceTestCase { parser.setInput(new BufferedInputStream( new ByteArrayInputStream(baos.toByteArray())), null); parser.nextTag(); - mListeners.readExtraTag("requested_listeners", parser); + mListeners.readExtraTag("req_listeners", parser); validateListenersFromXml(); } @@ -192,7 +192,7 @@ public class NotificationListenersTest extends UiServiceTestCase { assertThat(mListeners.getNotificationListenerFilter( Pair.create(si.getComponentName(), 0)).getTypes()) - .isEqualTo(7); + .isEqualTo(15); assertThat(mListeners.getNotificationListenerFilter(Pair.create(si.getComponentName(), 0)) .getDisallowedPackages()) .isEmpty(); 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 475e462bdd3d..009c011105de 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 com.google.common.truth.Truth.assertThat; 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 @@ import java.util.concurrent.TimeUnit; @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 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { } /** - * 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); - - assertThat(mSupervisor.mWaitingActivityLaunched).isEmpty(); + 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); + + // 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); - - assertThat(mSupervisor.mWaitingActivityLaunched).isEmpty(); + 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); + + 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 class ActivityTaskSupervisorTests extends WindowTestsBase { 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/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 6c824d6c87dc..ce5fc4021eac 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -83,7 +83,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN, AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null, null)); + null, null, false)); } @Test @@ -99,7 +99,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE, AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null, null)); + null, null, false)); } @Test @@ -117,7 +117,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { assertEquals(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE, AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null, null)); + null, null, false)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index 8cc515e83342..f1e36098d84e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_OLD_UNSET; import static android.view.WindowManager.TRANSIT_OPEN; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; @@ -81,7 +82,8 @@ public class AppTransitionTests extends WindowTestsBase { assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY, AppTransitionController.getTransitCompatType(mDc.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null /* wallpaperTarget */, null /* oldWallpaper */)); + null /* wallpaperTarget */, null /* oldWallpaper */, + false /*skipAppTransitionAnimation*/)); } @Test @@ -95,7 +97,8 @@ public class AppTransitionTests extends WindowTestsBase { assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY, AppTransitionController.getTransitCompatType(mDc.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null /* wallpaperTarget */, null /* oldWallpaper */)); + null /* wallpaperTarget */, null /* oldWallpaper */, + false /*skipAppTransitionAnimation*/)); } @Test @@ -109,7 +112,8 @@ public class AppTransitionTests extends WindowTestsBase { assertEquals(TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE, AppTransitionController.getTransitCompatType(mDc.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null /* wallpaperTarget */, null /* oldWallpaper */)); + null /* wallpaperTarget */, null /* oldWallpaper */, + false /*skipAppTransitionAnimation*/)); } @Test @@ -123,7 +127,23 @@ public class AppTransitionTests extends WindowTestsBase { assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY, AppTransitionController.getTransitCompatType(mDc.mAppTransition, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - null /* wallpaperTarget */, null /* oldWallpaper */)); + null /* wallpaperTarget */, null /* oldWallpaper */, + false /*skipAppTransitionAnimation*/)); + } + + @Test + public void testSkipTransitionAnimation() { + final DisplayContent dc = createNewDisplay(Display.STATE_ON); + final ActivityRecord activity = createActivityRecord(dc); + + mDc.prepareAppTransition(TRANSIT_OPEN); + mDc.prepareAppTransition(TRANSIT_CLOSE); + mDc.mClosingApps.add(activity); + assertEquals(TRANSIT_OLD_UNSET, + AppTransitionController.getTransitCompatType(mDc.mAppTransition, + mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, + null /* wallpaperTarget */, null /* oldWallpaper */, + true /*skipAppTransitionAnimation*/)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java index 3306e313f95e..2f4c8e256760 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java @@ -15,7 +15,6 @@ */ package com.android.server.wm; - import static android.os.Process.INVALID_UID; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -31,6 +30,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION; import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER; import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED; +import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL; import static android.window.DisplayAreaOrganizer.FEATURE_ROOT; import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST; @@ -103,6 +103,7 @@ public class DisplayAreaPolicyBuilderTest { mImeContainer = new DisplayArea.Tokens(mWms, ABOVE_TASKS, "ImeContainer"); mDisplayContent = mock(DisplayContent.class); doReturn(true).when(mDisplayContent).isTrusted(); + mDisplayContent.isDefaultDisplay = true; mDefaultTaskDisplayArea = new TaskDisplayArea(mDisplayContent, mWms, "Tasks", FEATURE_DEFAULT_TASK_CONTAINER); mTaskDisplayAreaList = new ArrayList<>(); @@ -183,13 +184,34 @@ public class DisplayAreaPolicyBuilderTest { final DisplayAreaPolicyBuilder.Result defaultPolicy = (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent, mRoot, mImeContainer); - final List<Feature> features = defaultPolicy.getFeatures(); - boolean hasOneHandedFeature = false; - for (int i = 0; i < features.size(); i++) { - hasOneHandedFeature |= features.get(i).getId() == FEATURE_ONE_HANDED; + if (mDisplayContent.isDefaultDisplay) { + final List<Feature> features = defaultPolicy.getFeatures(); + boolean hasOneHandedFeature = false; + for (Feature feature : features) { + hasOneHandedFeature |= feature.getId() == FEATURE_ONE_HANDED; + } + + assertThat(hasOneHandedFeature).isTrue(); } + } + + @Test + public void testBuilder_defaultPolicy_hasOneHandedBackgroundFeature() { + final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources( + resourcesWithProvider("")); + final DisplayAreaPolicyBuilder.Result defaultPolicy = + (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent, + mRoot, mImeContainer); + if (mDisplayContent.isDefaultDisplay) { + final List<Feature> features = defaultPolicy.getFeatures(); + boolean hasOneHandedBackgroundFeature = false; + for (Feature feature : features) { + hasOneHandedBackgroundFeature |= + feature.getId() == FEATURE_ONE_HANDED_BACKGROUND_PANEL; + } - assertThat(hasOneHandedFeature).isTrue(); + assertThat(hasOneHandedBackgroundFeature).isTrue(); + } } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 4b42d5603b55..673b00f25824 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -61,7 +61,6 @@ import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; -import android.graphics.Rect; import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; @@ -1145,8 +1144,6 @@ public class RecentTasksTest extends WindowTestsBase { () -> mAtm.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true)); assertSecurityException(expectCallable, () -> mAtm.moveTaskToRootTask(0, INVALID_STACK_ID, true)); - assertSecurityException(expectCallable, - () -> mAtm.moveTopActivityToPinnedRootTask(INVALID_STACK_ID, new Rect())); assertSecurityException(expectCallable, () -> mAtm.getAllRootTaskInfos()); assertSecurityException(expectCallable, () -> mAtm.getRootTaskInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED)); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 74248a950a38..401ace03c554 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -77,6 +77,7 @@ public class TransitionTests extends WindowTestsBase { changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); + fillChangeMap(changes, newTask); // End states. closing.mVisibleRequested = false; opening.mVisibleRequested = true; @@ -141,6 +142,7 @@ public class TransitionTests extends WindowTestsBase { changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(opening2, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); + fillChangeMap(changes, newTask); // End states. closing.mVisibleRequested = false; opening.mVisibleRequested = true; @@ -189,6 +191,8 @@ public class TransitionTests extends WindowTestsBase { changes.put(tda, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(showing, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(showing2, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + fillChangeMap(changes, tda); + // End states. showing.mVisibleRequested = true; showing2.mVisibleRequested = true; @@ -338,4 +342,12 @@ public class TransitionTests extends WindowTestsBase { assertEquals(FLAG_SHOW_WALLPAPER, info.getChange( tasks[showWallpaperTask].mRemoteToken.toWindowContainerToken()).getFlags()); } + + /** Fill the change map with all the parents of top. Change maps are usually fully populated */ + private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes, + WindowContainer top) { + for (WindowContainer curr = top.getParent(); curr != null; curr = curr.getParent()) { + changes.put(curr, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); + } + } } diff --git a/services/translation/Android.bp b/services/translation/Android.bp new file mode 100644 index 000000000000..804a6177e94e --- /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 000000000000..a1e663aa8ff7 --- /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 000000000000..0c7e61765501 --- /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 000000000000..e2aabe6a89ea --- /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 000000000000..b1f6f80d4158 --- /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/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index d585b2374783..afa35fe8deba 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -175,7 +175,10 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser // Delay for debouncing USB disconnects. // We often get rapid connect/disconnect events when enabling USB functions, // which need debouncing. - private static final int UPDATE_DELAY = 1000; + private static final int DEVICE_STATE_UPDATE_DELAY = 3000; + + // Delay for debouncing USB disconnects on Type-C ports in host mode + private static final int HOST_STATE_UPDATE_DELAY = 1000; // Timeout for entering USB request mode. // Request is cancelled if host does not configure device within 10 seconds. @@ -636,7 +639,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser msg.arg1 = connected; msg.arg2 = configured; // debounce disconnects to avoid problems bringing up USB tethering - sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0); + sendMessageDelayed(msg, (connected == 0) ? DEVICE_STATE_UPDATE_DELAY : 0); } public void updateHostState(UsbPort port, UsbPortStatus status) { @@ -651,7 +654,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser removeMessages(MSG_UPDATE_PORT_STATE); Message msg = obtainMessage(MSG_UPDATE_PORT_STATE, args); // debounce rapid transitions of connect/disconnect on type-c ports - sendMessageDelayed(msg, UPDATE_DELAY); + sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY); } private void setAdbEnabled(boolean enable) { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 56345c032670..7e019ccde372 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -743,6 +743,14 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool"; /** + * Flag specifying whether Cross SIM over IMS should be available for carrier. + * When {@code false} the carrier does not support cross SIM calling. + * When {@code true} the carrier does support cross sim calling, where available + */ + public static final String KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL = + "carrier_cross_sim_ims_available_bool"; + + /** * Specifies a map from dialstrings to replacements for roaming network service numbers which * cannot be replaced on the carrier side. * <p> @@ -1497,6 +1505,31 @@ public class CarrierConfigManager { "wfc_carrier_name_override_by_pnn_bool"; /** + * Value for {#CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING} cnfig. + * specifies SPN format of displaying carrier name only. + * + */ + public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_ONLY = 0; + + /** + * Value for {#CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING} cnfig. + * specifies SPN format of displaying carrier name along with "Cross-SIM calling". + */ + public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING = 1; + + /** + * Indexes of SPN format strings in crossSimSpnFormats. + * + * <p>Available options are: + * <ul> + * <li> {#CROSS_SIM_SPN_FORMAT_CARRIER_NAME_ONLY}: %s</li> + * <li> {#CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING}: %s Cross-SIM Calling</li> + * </ul> + * %s will be filled with carrier name + */ + public static final String KEY_CROSS_SIM_SPN_FORMAT_INT = "cross_sim_spn_format_int"; + + /** * Override the SPN Display Condition 2 integer bits (lsb). B2, B1 is the last two bits of the * spn display condition coding. * @@ -2373,7 +2406,8 @@ public class CarrierConfigManager { "show_blocking_pay_phone_option_bool"; /** - * Flag specifying whether the carrier will use the WFC home network mode in roaming network. + * Flag specifying whether the carrier will use the + * WFC home network mode in roaming network. * {@code false} - roaming preference can be selected separately from the home preference. * {@code true} - roaming preference is the same as home preference and * {@link #KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT} is used as the default value. @@ -4621,6 +4655,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true); sDefaults.putBoolean(KEY_VILTE_DATA_IS_METERED_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL, false); @@ -4794,6 +4829,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CARRIER_NAME_OVERRIDE_BOOL, false); sDefaults.putString(KEY_CARRIER_NAME_STRING, ""); sDefaults.putBoolean(KEY_WFC_CARRIER_NAME_OVERRIDE_BY_PNN_BOOL, false); + sDefaults.putInt(KEY_CROSS_SIM_SPN_FORMAT_INT, 1); sDefaults.putInt(KEY_SPN_DISPLAY_CONDITION_OVERRIDE_INT, -1); sDefaults.putStringArray(KEY_SPDI_OVERRIDE_STRING_ARRAY, null); sDefaults.putStringArray(KEY_PNN_OVERRIDE_STRING_ARRAY, null); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 60389e1547c5..a6e870fe7a16 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -5586,6 +5586,10 @@ public class TelephonyManager { */ @Deprecated public void listen(PhoneStateListener listener, int events) { + if (!listener.isExecutorSet()) { + throw new IllegalStateException("PhoneStateListener should be created on a thread " + + "with Looper.myLooper() != null"); + } boolean notifyNow = getITelephony() != null; mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class); if (mTelephonyRegistryMgr != null) { diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index e217e9af992c..ed1f3ddd6ba0 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.LinkAddress; +import android.net.LinkProperties; import android.os.Parcel; import android.os.Parcelable; import android.telephony.Annotation.DataFailureCause; @@ -276,9 +277,11 @@ public final class DataCallResponse implements Parcelable { } /** - * @return The network suggested data retry duration in milliseconds. {@code Long.MAX_VALUE} - * indicates data retry should not occur. {@link #RETRY_DURATION_UNDEFINED} indicates network - * did not suggest any retry duration. + * @return The network suggested data retry duration in milliseconds as specified in + * 3GPP TS 24.302 section 8.2.9.1. The APN associated to this data call will be throttled for + * the specified duration unless {@link DataServiceCallback#onApnUnthrottled} is called. + * {@code Long.MAX_VALUE} indicates data retry should not occur. + * {@link #RETRY_DURATION_UNDEFINED} indicates network did not suggest any retry duration. */ public long getRetryDurationMillis() { return mSuggestedRetryTime; diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index 52bf15fd16c3..f56c19b78a16 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -250,7 +250,11 @@ public class DataServiceCallback { } /** - * Indicates that the specified APN is no longer throttled. + * The APN is throttled for the duration specified in + * {@link DataCallResponse#getRetryDurationMillis}. Calling this method unthrottles that + * APN. + * <p/> + * see: {@link DataCallResponse#getRetryDurationMillis} * * @param apn Access Point Name defined by the carrier. */ diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt index d587f1e383c5..6bf2c855f08a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt @@ -19,8 +19,21 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation import androidx.test.uiautomator.UiDevice -class ImeAppAutoFocusHelper(instr: Instrumentation) : ImeAppHelper(instr, "ImeAppAutoFocus") { +class ImeAppAutoFocusHelper @JvmOverloads constructor( + instr: Instrumentation, + private val rotation: Int, + private val imePackageName: String = IME_PACKAGE +) : ImeAppHelper(instr, "ImeAppAutoFocus") { override fun openIME(device: UiDevice) { // do nothing (the app is focused automatically) } + + override fun open() { + val expectedPackage = if (rotation.isRotated()) { + imePackageName + } else { + packageName + } + launcherStrategy.launch(appName, expectedPackage) + } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt index b341e621d9ed..412a3c383785 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt @@ -61,10 +61,11 @@ class CloseImeAutoOpenWindowToAppTest( @JvmStatic fun getParams(): List<Array<Any>> { val instrumentation = InstrumentationRegistry.getInstrumentation() - val testApp = ImeAppAutoFocusHelper(instrumentation) return FlickerTestRunnerFactory(instrumentation) .buildTest { configuration -> + val testApp = ImeAppAutoFocusHelper(instrumentation, + configuration.startRotation) withTag { buildTestTag("imeToAppAutoOpen", testApp, configuration) } repeat { configuration.repetitions } setup { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index 51a4ca86681b..60a798fda5e4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -61,10 +61,11 @@ class CloseImeAutoOpenWindowToHomeTest( @JvmStatic fun getParams(): List<Array<Any>> { val instrumentation = InstrumentationRegistry.getInstrumentation() - val testApp = ImeAppAutoFocusHelper(instrumentation) return FlickerTestRunnerFactory(instrumentation) .buildTest { configuration -> + val testApp = ImeAppAutoFocusHelper(instrumentation, + configuration.startRotation) withTestName { buildTestTag("imeToHomeAutoOpen", testApp, configuration) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index c7114da50117..d1842739171d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -66,11 +66,12 @@ class ReOpenImeWindowTest( @JvmStatic fun getParams(): List<Array<Any>> { val instrumentation = InstrumentationRegistry.getInstrumentation() - val testApp = ImeAppAutoFocusHelper(instrumentation) val testAppComponentName = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME return FlickerTestRunnerFactory(instrumentation, repetitions = 5) .buildTest { configuration -> + val testApp = ImeAppAutoFocusHelper(instrumentation, + configuration.startRotation) withTestName { buildTestTag("reOpenImeAutoFocus", testApp, configuration) } repeat { configuration.repetitions } setup { diff --git a/tests/net/Android.bp b/tests/net/Android.bp index a7622198cec7..f6a2846c9b3c 100644 --- a/tests/net/Android.bp +++ b/tests/net/Android.bp @@ -70,4 +70,7 @@ android_test { "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 3433880f3c0e..e318207cdc75 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -1199,6 +1199,8 @@ public class ConnectivityServiceTest { updateState(NetworkInfo.DetailedState.DISCONNECTED, "disconnect"); } mAgentRegistered = false; + setUids(null); + mInterface = null; } @Override @@ -3624,51 +3626,55 @@ public class ConnectivityServiceTest { // 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()); - - ContentResolver cr = mServiceContext.getContentResolver(); - - // 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()); - - // 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. + try { + testFactory.waitForNetworkRequests(1); + assertTrue(testFactory.getMyStartRequested()); - // Check that cell data stays up. - waitForIdle(); - verifyActiveNetwork(TRANSPORT_WIFI); - assertLength(2, mCm.getAllNetworks()); + // 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 off mobile data always on and expect the request to disappear... - testFactory.expectRemoveRequests(1); - setAlwaysOnNetworks(false); - testFactory.waitForNetworkRequests(1); + ContentResolver cr = mServiceContext.getContentResolver(); + + // 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()); + + // 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. + + // 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 @@ -6176,11 +6182,15 @@ public class ConnectivityServiceTest { // 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 +6230,54 @@ public class ConnectivityServiceTest { && 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 diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java index b47be97ed002..cd4cfcf18804 100644 --- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java @@ -20,6 +20,7 @@ import static com.android.server.connectivity.NetworkNotificationManager.Notific import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -39,6 +40,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.os.UserHandle; import android.telephony.TelephonyManager; +import android.util.DisplayMetrics; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -46,7 +48,9 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.AdditionalAnswers; @@ -82,6 +86,7 @@ public class NetworkNotificationManagerTest { @Mock Context mCtx; @Mock Resources mResources; + @Mock DisplayMetrics mDisplayMetrics; @Mock PackageManager mPm; @Mock TelephonyManager mTelephonyManager; @Mock NotificationManager mNotificationManager; @@ -93,6 +98,17 @@ public class NetworkNotificationManagerTest { NetworkNotificationManager mManager; + + @BeforeClass + public static void setUpClass() { + Notification.DevFlags.sForceDefaults = true; + } + + @AfterClass + public static void tearDownClass() { + Notification.DevFlags.sForceDefaults = false; + } + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -103,6 +119,7 @@ public class NetworkNotificationManagerTest { mCellNai.networkInfo = mNetworkInfo; mVpnNai.networkCapabilities = VPN_CAPABILITIES; mVpnNai.networkInfo = mNetworkInfo; + mDisplayMetrics.density = 2.275f; doReturn(true).when(mVpnNai).isVPN(); when(mCtx.getResources()).thenReturn(mResources); when(mCtx.getPackageManager()).thenReturn(mPm); @@ -114,6 +131,7 @@ public class NetworkNotificationManagerTest { .thenReturn(mNotificationManager); when(mNetworkInfo.getExtraInfo()).thenReturn("extra"); when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B); + when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); mManager = new NetworkNotificationManager(mCtx, mTelephonyManager); } @@ -136,15 +154,15 @@ public class NetworkNotificationManagerTest { public void testTitleOfPrivateDnsBroken() { // Test the title of mobile data. verifyTitleByNetwork(100, mCellNai, R.string.mobile_no_internet); - reset(mResources); + clearInvocations(mResources); // Test the title of wifi. verifyTitleByNetwork(101, mWifiNai, R.string.wifi_no_internet); - reset(mResources); + clearInvocations(mResources); // Test the title of other networks. verifyTitleByNetwork(102, mVpnNai, R.string.other_networks_no_internet); - reset(mResources); + clearInvocations(mResources); } @Test diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 3648c4db1e16..02a2aadc4c79 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { } @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 @@ public class VpnTest { 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. */ diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java index 89146f945e1f..435c3c0af817 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java @@ -64,7 +64,6 @@ import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; @@ -124,7 +123,7 @@ public class NetworkStatsCollectionTest { // now export into a unified format final ByteArrayOutputStream bos = new ByteArrayOutputStream(); - collection.write(new DataOutputStream(bos)); + collection.write(bos); // clear structure completely collection.reset(); @@ -152,7 +151,7 @@ public class NetworkStatsCollectionTest { // now export into a unified format final ByteArrayOutputStream bos = new ByteArrayOutputStream(); - collection.write(new DataOutputStream(bos)); + collection.write(bos); // clear structure completely collection.reset(); @@ -180,7 +179,7 @@ public class NetworkStatsCollectionTest { // now export into a unified format final ByteArrayOutputStream bos = new ByteArrayOutputStream(); - collection.write(new DataOutputStream(bos)); + collection.write(bos); // clear structure completely collection.reset(); diff --git a/tests/vcn/java/android/net/vcn/VcnConfigTest.java b/tests/vcn/java/android/net/vcn/VcnConfigTest.java index 77944deb26f1..c1ef350e5c4a 100644 --- a/tests/vcn/java/android/net/vcn/VcnConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnConfigTest.java @@ -18,12 +18,17 @@ package android.net.vcn; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import android.annotation.NonNull; +import android.content.Context; import android.os.Parcel; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,12 +38,15 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest public class VcnConfigTest { + private static final String TEST_PACKAGE_NAME = VcnConfigTest.class.getPackage().getName(); private static final Set<VcnGatewayConnectionConfig> GATEWAY_CONNECTION_CONFIGS = Collections.singleton(VcnGatewayConnectionConfigTest.buildTestConfig()); + private final Context mContext = mock(Context.class); + // Public visibility for VcnManagementServiceTest - public static VcnConfig buildTestConfig() { - VcnConfig.Builder builder = new VcnConfig.Builder(); + public static VcnConfig buildTestConfig(@NonNull Context context) { + VcnConfig.Builder builder = new VcnConfig.Builder(context); for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) { builder.addGatewayConnectionConfig(gatewayConnectionConfig); @@ -47,10 +55,24 @@ public class VcnConfigTest { return builder.build(); } + @Before + public void setUp() throws Exception { + doReturn(TEST_PACKAGE_NAME).when(mContext).getOpPackageName(); + } + + @Test + public void testBuilderConstructorRequiresContext() { + try { + new VcnConfig.Builder(null); + fail("Expected exception due to null context"); + } catch (NullPointerException e) { + } + } + @Test public void testBuilderRequiresGatewayConnectionConfig() { try { - new VcnConfig.Builder().build(); + new VcnConfig.Builder(mContext).build(); fail("Expected exception due to no VcnGatewayConnectionConfigs provided"); } catch (IllegalArgumentException e) { } @@ -58,21 +80,22 @@ public class VcnConfigTest { @Test public void testBuilderAndGetters() { - final VcnConfig config = buildTestConfig(); + final VcnConfig config = buildTestConfig(mContext); + assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName()); assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs()); } @Test public void testPersistableBundle() { - final VcnConfig config = buildTestConfig(); + final VcnConfig config = buildTestConfig(mContext); assertEquals(config, new VcnConfig(config.toPersistableBundle())); } @Test public void testParceling() { - final VcnConfig config = buildTestConfig(); + final VcnConfig config = buildTestConfig(mContext); Parcel parcel = Parcel.obtain(); config.writeToParcel(parcel, 0); diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 1cc953239fed..696110f01869 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -16,16 +16,23 @@ package com.android.server; +import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.app.AppOpsManager; import android.content.Context; import android.net.ConnectivityManager; import android.net.vcn.VcnConfig; @@ -42,29 +49,47 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.vcn.TelephonySubscriptionTracker; +import com.android.server.vcn.Vcn; +import com.android.server.vcn.VcnContext; +import com.android.server.vcn.VcnNetworkProvider; import com.android.server.vcn.util.PersistableBundleUtils; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import java.io.FileNotFoundException; import java.util.Collections; import java.util.Map; +import java.util.Set; import java.util.UUID; /** Tests for {@link VcnManagementService}. */ @RunWith(AndroidJUnit4.class) @SmallTest public class VcnManagementServiceTest { + private static final String TEST_PACKAGE_NAME = + VcnManagementServiceTest.class.getPackage().getName(); private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0)); private static final ParcelUuid TEST_UUID_2 = new ParcelUuid(new UUID(1, 1)); - private static final VcnConfig TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig(); + private static final VcnConfig TEST_VCN_CONFIG; + private static final int TEST_UID = Process.FIRST_APPLICATION_UID; + + static { + final Context mockConfigContext = mock(Context.class); + doReturn(TEST_PACKAGE_NAME).when(mockConfigContext).getOpPackageName(); + + TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig(mockConfigContext); + } + private static final Map<ParcelUuid, VcnConfig> TEST_VCN_CONFIG_MAP = Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG)); + private static final int TEST_SUBSCRIPTION_ID = 1; private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO = new SubscriptionInfo( - 1 /* id */, + TEST_SUBSCRIPTION_ID /* id */, "" /* iccId */, 0 /* simSlotIndex */, "Carrier" /* displayName */, @@ -92,22 +117,48 @@ public class VcnManagementServiceTest { private final ConnectivityManager mConnMgr = mock(ConnectivityManager.class); private final TelephonyManager mTelMgr = mock(TelephonyManager.class); private final SubscriptionManager mSubMgr = mock(SubscriptionManager.class); - private final VcnManagementService mVcnMgmtSvc; + private final AppOpsManager mAppOpsMgr = mock(AppOpsManager.class); + private final VcnContext mVcnContext = mock(VcnContext.class); private final PersistableBundleUtils.LockingReadWriteHelper mConfigReadWriteHelper = mock(PersistableBundleUtils.LockingReadWriteHelper.class); + private final TelephonySubscriptionTracker mSubscriptionTracker = + mock(TelephonySubscriptionTracker.class); + + private final VcnManagementService mVcnMgmtSvc; public VcnManagementServiceTest() throws Exception { setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class); setupSystemService( mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class); + setupSystemService(mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class); + + doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName(); doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper(); - doReturn(Process.FIRST_APPLICATION_UID).when(mMockDeps).getBinderCallingUid(); + doReturn(TEST_UID).when(mMockDeps).getBinderCallingUid(); + doReturn(mVcnContext) + .when(mMockDeps) + .newVcnContext( + eq(mMockContext), + eq(mTestLooper.getLooper()), + any(VcnNetworkProvider.class)); + doReturn(mSubscriptionTracker) + .when(mMockDeps) + .newTelephonySubscriptionTracker( + eq(mMockContext), + eq(mTestLooper.getLooper()), + any(TelephonySubscriptionTrackerCallback.class)); doReturn(mConfigReadWriteHelper) .when(mMockDeps) .newPersistableBundleLockingReadWriteHelper(any()); + // Setup VCN instance generation + doAnswer((invocation) -> { + // Mock-within a doAnswer is safe, because it doesn't actually run nested. + return mock(Vcn.class); + }).when(mMockDeps).newVcn(any(), any(), any()); + final PersistableBundle bundle = PersistableBundleUtils.fromMap( TEST_VCN_CONFIG_MAP, @@ -117,6 +168,9 @@ public class VcnManagementServiceTest { setupMockedCarrierPrivilege(true); mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps); + + // Make sure the profiles are loaded. + mTestLooper.dispatchAll(); } private void setupSystemService(Object service, String name, Class<?> serviceClass) { @@ -137,8 +191,8 @@ public class VcnManagementServiceTest { public void testSystemReady() throws Exception { mVcnMgmtSvc.systemReady(); - verify(mConnMgr) - .registerNetworkProvider(any(VcnManagementService.VcnNetworkProvider.class)); + verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class)); + verify(mSubscriptionTracker).register(); } @Test @@ -171,12 +225,110 @@ public class VcnManagementServiceTest { verify(mConfigReadWriteHelper).readFromDisk(); } + private void triggerSubscriptionTrackerCallback(Set<ParcelUuid> activeSubscriptionGroups) { + final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class); + doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups(); + + final Set<String> privilegedPackages = + (activeSubscriptionGroups == null || activeSubscriptionGroups.isEmpty()) + ? Collections.emptySet() + : Collections.singleton(TEST_PACKAGE_NAME); + doReturn(true) + .when(snapshot) + .packageHasPermissionsForSubscriptionGroup( + argThat(val -> activeSubscriptionGroups.contains(val)), + eq(TEST_PACKAGE_NAME)); + + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + cb.onNewSnapshot(snapshot); + } + + private TelephonySubscriptionTrackerCallback getTelephonySubscriptionTrackerCallback() { + final ArgumentCaptor<TelephonySubscriptionTrackerCallback> captor = + ArgumentCaptor.forClass(TelephonySubscriptionTrackerCallback.class); + verify(mMockDeps) + .newTelephonySubscriptionTracker( + eq(mMockContext), eq(mTestLooper.getLooper()), captor.capture()); + return captor.getValue(); + } + + private Vcn startAndGetVcnInstance(ParcelUuid uuid) { + mVcnMgmtSvc.setVcnConfig(uuid, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + return mVcnMgmtSvc.getAllVcns().get(uuid); + } + + @Test + public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception { + triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_1)); + verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG)); + } + + @Test + public void testTelephonyNetworkTrackerCallbackStopsInstances() throws Exception { + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); + + triggerSubscriptionTrackerCallback(Collections.emptySet()); + + // Verify teardown after delay + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + verify(vcn).teardownAsynchronously(); + } + + @Test + public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances() + throws Exception { + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); + + // Simulate SIM unloaded + triggerSubscriptionTrackerCallback(Collections.emptySet()); + + // Simulate new SIM loaded right during teardown delay. + mTestLooper.moveTimeForward( + VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); + mTestLooper.dispatchAll(); + triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_2)); + + // Verify that even after the full timeout duration, the VCN instance is not torn down + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + verify(vcn, never()).teardownAsynchronously(); + } + + @Test + public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception { + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2); + + // Simulate SIM unloaded + triggerSubscriptionTrackerCallback(Collections.emptySet()); + + // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new + // vcnInstance. + mTestLooper.moveTimeForward( + VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); + mTestLooper.dispatchAll(); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2); + final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2); + + // Verify that new instance was different, and the old one was torn down + assertTrue(oldInstance != newInstance); + verify(oldInstance).teardownAsynchronously(); + + // Verify that even after the full timeout duration, the new VCN instance is not torn down + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + verify(newInstance, never()).teardownAsynchronously(); + } + @Test public void testSetVcnConfigRequiresNonSystemServer() throws Exception { doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid(); try { - mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig()); + mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected IllegalStateException exception for system server"); } catch (IllegalStateException expected) { } @@ -184,12 +336,12 @@ public class VcnManagementServiceTest { @Test public void testSetVcnConfigRequiresSystemUser() throws Exception { - doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID)) + doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID)) .when(mMockDeps) .getBinderCallingUid(); try { - mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig()); + mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected security exception for non system user"); } catch (SecurityException expected) { } @@ -200,16 +352,25 @@ public class VcnManagementServiceTest { setupMockedCarrierPrivilege(false); try { - mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig()); + mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected security exception for missing carrier privileges"); } catch (SecurityException expected) { } } @Test + public void testSetVcnConfigMismatchedPackages() throws Exception { + try { + mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, "IncorrectPackage"); + fail("Expected exception due to mismatched packages in config and method call"); + } catch (IllegalArgumentException expected) { + } + } + + @Test public void testSetVcnConfig() throws Exception { // Use a different UUID to simulate a new VCN config. - mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG); + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); assertEquals(TEST_VCN_CONFIG, mVcnMgmtSvc.getConfigs().get(TEST_UUID_2)); verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); } @@ -227,7 +388,7 @@ public class VcnManagementServiceTest { @Test public void testClearVcnConfigRequiresSystemUser() throws Exception { - doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID)) + doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID)) .when(mMockDeps) .getBinderCallingUid(); @@ -255,4 +416,26 @@ public class VcnManagementServiceTest { assertTrue(mVcnMgmtSvc.getConfigs().isEmpty()); verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); } + + @Test + public void testSetVcnConfigClearVcnConfigStartsUpdatesAndTeardsDownVcns() throws Exception { + // Use a different UUID to simulate a new VCN config. + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + final Map<ParcelUuid, Vcn> vcnInstances = mVcnMgmtSvc.getAllVcns(); + final Vcn vcnInstance = vcnInstances.get(TEST_UUID_2); + assertEquals(1, vcnInstances.size()); + assertEquals(TEST_VCN_CONFIG, mVcnMgmtSvc.getConfigs().get(TEST_UUID_2)); + verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); + + // Verify Vcn is started + verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG)); + + // Verify Vcn is updated if it was previously started + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + verify(vcnInstance).updateConfig(TEST_VCN_CONFIG); + + // Verify Vcn is stopped if it was already started + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2); + verify(vcnInstance).teardownAsynchronously(); + } } diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java index 17b8f64a13fa..528f240b9912 100644 --- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -30,6 +30,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; @@ -37,6 +38,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonMap; + import android.annotation.NonNull; import android.content.Context; import android.content.Intent; @@ -49,6 +54,7 @@ import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; +import android.telephony.TelephonyManager; import android.util.ArraySet; import androidx.test.filters.SmallTest; @@ -63,6 +69,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -71,12 +78,16 @@ import java.util.UUID; @RunWith(AndroidJUnit4.class) @SmallTest public class TelephonySubscriptionTrackerTest { + private static final String PACKAGE_NAME = + TelephonySubscriptionTrackerTest.class.getPackage().getName(); private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID()); private static final int TEST_SIM_SLOT_INDEX = 1; private static final int TEST_SUBSCRIPTION_ID_1 = 2; private static final SubscriptionInfo TEST_SUBINFO_1 = mock(SubscriptionInfo.class); private static final int TEST_SUBSCRIPTION_ID_2 = 3; private static final SubscriptionInfo TEST_SUBINFO_2 = mock(SubscriptionInfo.class); + private static final Map<ParcelUuid, Set<String>> TEST_PRIVILEGED_PACKAGES = + Collections.singletonMap(TEST_PARCEL_UUID, Collections.singleton(PACKAGE_NAME)); private static final Map<Integer, ParcelUuid> TEST_SUBID_TO_GROUP_MAP; static { @@ -91,6 +102,7 @@ public class TelephonySubscriptionTrackerTest { @NonNull private final Handler mHandler; @NonNull private final TelephonySubscriptionTracker.Dependencies mDeps; + @NonNull private final TelephonyManager mTelephonyManager; @NonNull private final SubscriptionManager mSubscriptionManager; @NonNull private final CarrierConfigManager mCarrierConfigManager; @@ -103,9 +115,15 @@ public class TelephonySubscriptionTrackerTest { mHandler = new Handler(mTestLooper.getLooper()); mDeps = mock(TelephonySubscriptionTracker.Dependencies.class); + mTelephonyManager = mock(TelephonyManager.class); mSubscriptionManager = mock(SubscriptionManager.class); mCarrierConfigManager = mock(CarrierConfigManager.class); + doReturn(Context.TELEPHONY_SERVICE) + .when(mContext) + .getSystemServiceName(TelephonyManager.class); + doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE); + doReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE) .when(mContext) .getSystemServiceName(SubscriptionManager.class); @@ -140,6 +158,9 @@ public class TelephonySubscriptionTrackerTest { doReturn(Arrays.asList(TEST_SUBINFO_1, TEST_SUBINFO_2)) .when(mSubscriptionManager) .getAllSubscriptionInfoList(); + + doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt()); + setPrivilegedPackagesForMock(Collections.singletonList(PACKAGE_NAME)); } private IntentFilter getIntentFilter() { @@ -167,13 +188,15 @@ public class TelephonySubscriptionTrackerTest { return intent; } - private TelephonySubscriptionSnapshot buildExpectedSnapshot(Set<ParcelUuid> activeSubGroups) { - return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, activeSubGroups); + private TelephonySubscriptionSnapshot buildExpectedSnapshot( + Map<ParcelUuid, Set<String>> privilegedPackages) { + return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, privilegedPackages); } private TelephonySubscriptionSnapshot buildExpectedSnapshot( - Map<Integer, ParcelUuid> subIdToGroupMap, Set<ParcelUuid> activeSubGroups) { - return new TelephonySubscriptionSnapshot(subIdToGroupMap, activeSubGroups); + Map<Integer, ParcelUuid> subIdToGroupMap, + Map<ParcelUuid, Set<String>> privilegedPackages) { + return new TelephonySubscriptionSnapshot(subIdToGroupMap, privilegedPackages); } private void verifyNoActiveSubscriptions() { @@ -186,6 +209,10 @@ public class TelephonySubscriptionTrackerTest { Collections.singletonMap(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1)); } + private void setPrivilegedPackagesForMock(@NonNull List<String> privilegedPackages) { + doReturn(privilegedPackages).when(mTelephonyManager).getPackagesWithCarrierPrivileges(); + } + @Test public void testRegister() throws Exception { verify(mContext) @@ -223,15 +250,30 @@ public class TelephonySubscriptionTrackerTest { } @Test - public void testOnSubscriptionsChangedFired_WithReadySubIds() throws Exception { + public void testOnSubscriptionsChangedFired_WithReadySubidsNoPrivilegedPackages() + throws Exception { setupReadySubIds(); + setPrivilegedPackagesForMock(Collections.emptyList()); final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener(); listener.onSubscriptionsChanged(); mTestLooper.dispatchAll(); - final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID); - verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups))); + final Map<ParcelUuid, Set<String>> privilegedPackages = + Collections.singletonMap(TEST_PARCEL_UUID, new ArraySet<>()); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(privilegedPackages))); + } + + @Test + public void testOnSubscriptionsChangedFired_WithReadySubidsAndPrivilegedPackages() + throws Exception { + setupReadySubIds(); + + final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener(); + listener.onSubscriptionsChanged(); + mTestLooper.dispatchAll(); + + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); } @Test @@ -239,8 +281,7 @@ public class TelephonySubscriptionTrackerTest { mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); mTestLooper.dispatchAll(); - final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID); - verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups))); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); } @Test @@ -253,8 +294,7 @@ public class TelephonySubscriptionTrackerTest { mTestLooper.dispatchAll(); // Expect an empty snapshot - verify(mCallback).onNewSnapshot( - eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet()))); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap()))); } @Test @@ -281,41 +321,57 @@ public class TelephonySubscriptionTrackerTest { @Test public void testSubscriptionsClearedAfterValidTriggersCallbacks() throws Exception { - final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID); - mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); mTestLooper.dispatchAll(); - verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups))); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); assertNotNull( mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); doReturn(Collections.emptyList()).when(mSubscriptionManager).getAllSubscriptionInfoList(); mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); mTestLooper.dispatchAll(); - verify(mCallback).onNewSnapshot( - eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet()))); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap()))); } @Test public void testSlotClearedAfterValidTriggersCallbacks() throws Exception { - final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID); - mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); mTestLooper.dispatchAll(); - verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups))); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); assertNotNull( mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false)); mTestLooper.dispatchAll(); - verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(Collections.emptySet()))); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap()))); assertNull(mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); } @Test + public void testChangingPrivilegedPackagesAfterValidTriggersCallbacks() throws Exception { + setupReadySubIds(); + + // Setup initial "valid" state + final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener(); + listener.onSubscriptionsChanged(); + mTestLooper.dispatchAll(); + + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); + + // Simulate a loss of carrier privileges + setPrivilegedPackagesForMock(Collections.emptyList()); + listener.onSubscriptionsChanged(); + mTestLooper.dispatchAll(); + + verify(mCallback) + .onNewSnapshot( + eq(buildExpectedSnapshot(singletonMap(TEST_PARCEL_UUID, emptySet())))); + } + + @Test public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception { final TelephonySubscriptionSnapshot snapshot = - new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet()); + new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, emptyMap()); assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_1)); assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2)); @@ -324,7 +380,7 @@ public class TelephonySubscriptionTrackerTest { @Test public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception { final TelephonySubscriptionSnapshot snapshot = - new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet()); + new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, emptyMap()); assertEquals( new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)), diff --git a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java new file mode 100644 index 000000000000..c2c6200fd5f9 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java @@ -0,0 +1,121 @@ +/* + * 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.vcn; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.os.test.TestLooper; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +/** Tests for TelephonySubscriptionTracker */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VcnNetworkProviderTest { + private static final int TEST_SCORE_UNSATISFIED = 0; + private static final int TEST_SCORE_HIGH = 100; + private static final int TEST_PROVIDER_ID = 1; + private static final int TEST_LEGACY_TYPE = ConnectivityManager.TYPE_MOBILE; + private static final NetworkRequest.Type TEST_REQUEST_TYPE = NetworkRequest.Type.REQUEST; + + @NonNull private final Context mContext; + @NonNull private final TestLooper mTestLooper; + + @NonNull private VcnNetworkProvider mVcnNetworkProvider; + @NonNull private NetworkRequestListener mListener; + + public VcnNetworkProviderTest() { + mContext = mock(Context.class); + mTestLooper = new TestLooper(); + } + + @Before + public void setUp() throws Exception { + mVcnNetworkProvider = new VcnNetworkProvider(mContext, mTestLooper.getLooper()); + mListener = mock(NetworkRequestListener.class); + } + + @Test + public void testRequestsPassedToRegisteredListeners() throws Exception { + mVcnNetworkProvider.registerListener(mListener); + + final NetworkRequest request = mock(NetworkRequest.class); + mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID); + verify(mListener).onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID); + } + + @Test + public void testRequestsPassedToRegisteredListeners_satisfiedByHighScoringProvider() + throws Exception { + mVcnNetworkProvider.registerListener(mListener); + + final NetworkRequest request = mock(NetworkRequest.class); + mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_HIGH, TEST_PROVIDER_ID); + verify(mListener).onNetworkRequested(request, TEST_SCORE_HIGH, TEST_PROVIDER_ID); + } + + @Test + public void testUnregisterListener() throws Exception { + mVcnNetworkProvider.registerListener(mListener); + mVcnNetworkProvider.unregisterListener(mListener); + + final NetworkRequest request = mock(NetworkRequest.class); + mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID); + verifyNoMoreInteractions(mListener); + } + + @Test + public void testCachedRequestsPassedOnRegister() throws Exception { + final List<NetworkRequest> requests = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + final NetworkRequest request = + new NetworkRequest( + new NetworkCapabilities(), + TEST_LEGACY_TYPE, + i /* requestId */, + TEST_REQUEST_TYPE); + + requests.add(request); + mVcnNetworkProvider.onNetworkRequested(request, i, i + 1); + } + + mVcnNetworkProvider.registerListener(mListener); + for (int i = 0; i < requests.size(); i++) { + final NetworkRequest request = requests.get(i); + verify(mListener).onNetworkRequested(request, i, i + 1); + } + verifyNoMoreInteractions(mListener); + } +} diff --git a/tools/powerstats/PowerStatsServiceProtoParser.java b/tools/powerstats/PowerStatsServiceProtoParser.java index 76edd6341b55..97a2a402b537 100644 --- a/tools/powerstats/PowerStatsServiceProtoParser.java +++ b/tools/powerstats/PowerStatsServiceProtoParser.java @@ -90,6 +90,38 @@ public class PowerStatsServiceProtoParser { } } + private static void printPowerEntityInfo(PowerStatsServiceResidencyProto proto) { + String csvHeader = new String(); + for (int i = 0; i < proto.getPowerEntityInfoCount(); i++) { + PowerEntityInfoProto powerEntityInfo = proto.getPowerEntityInfo(i); + csvHeader += powerEntityInfo.getPowerEntityId() + "," + + powerEntityInfo.getPowerEntityName() + ","; + for (int j = 0; j < powerEntityInfo.getStatesCount(); j++) { + StateInfoProto stateInfo = powerEntityInfo.getStates(j); + csvHeader += stateInfo.getStateId() + "," + stateInfo.getStateName() + ","; + } + } + System.out.println(csvHeader); + } + + private static void printStateResidencyResult(PowerStatsServiceResidencyProto proto) { + for (int i = 0; i < proto.getStateResidencyResultCount(); i++) { + String csvRow = new String(); + + StateResidencyResultProto stateResidencyResult = proto.getStateResidencyResult(i); + csvRow += stateResidencyResult.getPowerEntityId() + ","; + + for (int j = 0; j < stateResidencyResult.getStateResidencyDataCount(); j++) { + StateResidencyProto stateResidency = stateResidencyResult.getStateResidencyData(j); + csvRow += stateResidency.getStateId() + "," + + stateResidency.getTotalTimeInStateMs() + "," + + stateResidency.getTotalStateEntryCount() + "," + + stateResidency.getLastEntryTimestampMs() + ","; + } + System.out.println(csvRow); + } + } + private static void generateCsvFile(String pathToIncidentReport) { try { // Print power meter data. @@ -115,6 +147,21 @@ public class PowerStatsServiceProtoParser { } else { System.out.println("Model incident report not found. Exiting."); } + + // Print state residency data. + IncidentReportResidencyProto irResidencyProto = + IncidentReportResidencyProto.parseFrom( + new FileInputStream(pathToIncidentReport)); + + if (irResidencyProto.hasIncidentReport()) { + PowerStatsServiceResidencyProto pssResidencyProto = + irResidencyProto.getIncidentReport(); + printPowerEntityInfo(pssResidencyProto); + printStateResidencyResult(pssResidencyProto); + } else { + System.out.println("Residency incident report not found. Exiting."); + } + } catch (IOException e) { System.out.println("Unable to open incident report file: " + pathToIncidentReport); System.out.println(e); |