diff options
884 files changed, 27122 insertions, 11859 deletions
diff --git a/ApiDocs.bp b/ApiDocs.bp index c82fee0fe8ac..faa0e5dba997 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -65,7 +65,7 @@ stubs_defaults { "test-base/src/**/*.java", ":opt-telephony-srcs", ":opt-net-voip-srcs", - ":art-module-public-api-stubs-source", + ":art.module.public.api{.public.stubs.source}", ":conscrypt.module.public.api{.public.stubs.source}", ":android_icu4j_public_api_files", "test-mock/src/**/*.java", @@ -135,7 +135,7 @@ doc_defaults { ], knowntags: [ "docs/knowntags.txt", - ":known-oj-tags", + ":art.module.public.api{.doctags}", ], custom_template: "droiddoc-templates-sdk", resourcesdir: "docs/html/reference/images/", diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 7a8d1a1caf25..f66d12a69594 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -11,6 +11,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp libs/input/ services/core/jni/ services/incremental/ + tests/ tools/ [Hook Scripts] diff --git a/StubLibraries.bp b/StubLibraries.bp index 852fcc6128c4..a3a209458679 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -47,7 +47,7 @@ stubs_defaults { "core/java/**/*.logtags", ":opt-telephony-srcs", ":opt-net-voip-srcs", - ":art-module-public-api-stubs-source", + ":art.module.public.api{.public.stubs.source}", ":android_icu4j_public_api_files", "**/package.html", ], diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml index 833ae63e1125..e0c11cf0e3d5 100644 --- a/apct-tests/perftests/core/AndroidManifest.xml +++ b/apct-tests/perftests/core/AndroidManifest.xml @@ -20,7 +20,17 @@ <action android:name="com.android.perftests.core.PERFTEST" /> </intent-filter> </activity> - <service android:name="android.os.SomeService" android:exported="false" android:process=":some_service" /> + + <service + android:name="android.os.SomeService" + android:exported="false" + android:process=":some_service" /> + + <provider + android:name="android.os.SomeProvider" + android:authorities="android.os.SomeProvider" + android:exported="false" + android:process=":some_provider" /> <service android:name="android.view.autofill.MyAutofillService" diff --git a/apct-tests/perftests/core/src/android/database/CrossProcessCursorPerfTest.java b/apct-tests/perftests/core/src/android/database/CrossProcessCursorPerfTest.java new file mode 100644 index 000000000000..77654df1273c --- /dev/null +++ b/apct-tests/perftests/core/src/android/database/CrossProcessCursorPerfTest.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 android.database; + +import static org.junit.Assert.assertEquals; + +import android.content.ContentProviderClient; +import android.content.ContentValues; +import android.content.Intent; +import android.net.Uri; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class CrossProcessCursorPerfTest { + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + /** + * Measure transporting a small {@link Cursor}, roughly 1KB in size. + */ + @Test + public void timeSmall() throws Exception { + time(1); + } + + /** + * Measure transporting a small {@link Cursor}, roughly 54KB in size. + */ + @Test + public void timeMedium() throws Exception { + time(100); + } + + /** + * Measure transporting a small {@link Cursor}, roughly 5.4MB in size. + */ + @Test + public void timeLarge() throws Exception { + time(10_000); + } + + private static final Uri TEST_URI = Uri.parse("content://android.os.SomeProvider/"); + + private void time(int count) throws Exception { + try (ContentProviderClient client = InstrumentationRegistry.getTargetContext() + .getContentResolver().acquireContentProviderClient(TEST_URI)) { + // Configure remote side once with data size to return + final ContentValues values = new ContentValues(); + values.put(Intent.EXTRA_INDEX, count); + client.update(TEST_URI, values, null); + + // Repeatedly query that data until we reach convergence + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + try (Cursor c = client.query(TEST_URI, null, null, null)) { + // Actually walk the returned values to ensure we pull all + // data from the remote side + while (c.moveToNext()) { + assertEquals(c.getPosition(), c.getInt(0)); + } + } + } + } + } +} diff --git a/apct-tests/perftests/core/src/android/os/SomeProvider.java b/apct-tests/perftests/core/src/android/os/SomeProvider.java new file mode 100644 index 000000000000..f5e247ea6e93 --- /dev/null +++ b/apct-tests/perftests/core/src/android/os/SomeProvider.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package android.os; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Intent; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; + +import java.util.Arrays; + +public class SomeProvider extends ContentProvider { + private Cursor mCursor; + + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + return mCursor; + } + + @Override + public String getType(Uri uri) { + throw new UnsupportedOperationException(); + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + throw new UnsupportedOperationException(); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException(); + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + final char[] valueRaw = new char[512]; + Arrays.fill(valueRaw, '!'); + final String value = new String(valueRaw); + + final int count = values.getAsInteger(Intent.EXTRA_INDEX); + final MatrixCursor cursor = new MatrixCursor(new String[] { "_id", "value" }); + for (int i = 0; i < count; i++) { + MatrixCursor.RowBuilder row = cursor.newRow(); + row.add(0, i); + row.add(1, value); + } + mCursor = cursor; + return 1; + } +} diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java index a701f8631969..ecd149989ce6 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java @@ -139,7 +139,6 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase final IntSupplier mViewVisibility; - int mSeq; int mFrameNumber; int mFlags; @@ -156,7 +155,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase void runBenchmark(BenchmarkState state) throws RemoteException { final IWindowSession session = WindowManagerGlobal.getWindowSession(); while (state.keepRunning()) { - session.relayout(mWindow, mSeq, mParams, mWidth, mHeight, + session.relayout(mWindow, mParams, mWidth, mHeight, mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls, mOutSurfaceSize, mOutBlastSurfaceControl); diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java index c52b1300aedc..b11d74646d67 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java @@ -107,7 +107,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase final InputChannel inputChannel = new InputChannel(); long startTime = SystemClock.elapsedRealtimeNanos(); - session.addToDisplay(this, mSeq, mLayoutParams, View.VISIBLE, + session.addToDisplay(this, mLayoutParams, View.VISIBLE, Display.DEFAULT_DISPLAY, mOutFrame, mOutContentInsets, mOutStableInsets, mOutDisplayCutout, inputChannel, mOutInsetsState, mOutControls); final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime; diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java index 9afa19475bef..7d2b64e5d882 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java @@ -47,6 +47,10 @@ import java.util.Objects; public class AppSearchDocument { private static final String TAG = "AppSearchDocument"; + /** The default empty namespace.*/ + // TODO(adorokhine): Allow namespace to be specified in the document. + public static final String DEFAULT_NAMESPACE = ""; + /** * The maximum number of elements in a repeatable field. Will reject the request if exceed * this limit. @@ -450,7 +454,7 @@ public class AppSearchDocument { */ public Builder(@NonNull String uri, @NonNull String schemaType) { mBuilderTypeInstance = (BuilderType) this; - mProtoBuilder.setUri(uri).setSchema(schemaType); + mProtoBuilder.setUri(uri).setSchema(schemaType).setNamespace(DEFAULT_NAMESPACE); // Set current timestamp for creation timestamp by default. setCreationTimestampMillis(System.currentTimeMillis()); } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchException.java b/apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java index 9b705ceb80de..00f6e75afe54 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchException.java +++ b/apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.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. @@ -14,31 +14,38 @@ * limitations under the License. */ -package com.android.server.appsearch; +package android.app.appsearch.exceptions; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appsearch.AppSearchResult; /** - * An exception thrown by {@link com.android.server.appsearch.AppSearchManagerService} or a - * subcomponent. + * An exception thrown by {@code android.app.appsearch.AppSearchManager} or a subcomponent. * - * <p>These exceptions can be converted into a failed {@link android.app.appsearch.AppSearchResult} + * <p>These exceptions can be converted into a failed {@link AppSearchResult} * for propagating to the client. + * @hide */ +//TODO(b/157082794): Linkify to AppSearchManager once that API is public public class AppSearchException extends Exception { private final @AppSearchResult.ResultCode int mResultCode; - /** Initializes an {@link com.android.server.appsearch.AppSearchException} with no message. */ + /** + * Initializes an {@link AppSearchException} with no message. + * @hide + */ public AppSearchException(@AppSearchResult.ResultCode int resultCode) { this(resultCode, /*message=*/ null); } + /** @hide */ public AppSearchException( @AppSearchResult.ResultCode int resultCode, @Nullable String message) { this(resultCode, message, /*cause=*/ null); } + /** @hide */ public AppSearchException( @AppSearchResult.ResultCode int resultCode, @Nullable String message, @@ -48,9 +55,9 @@ public class AppSearchException extends Exception { } /** - * Converts this {@link java.lang.Exception} into a failed - * {@link android.app.appsearch.AppSearchResult} + * Converts this {@link java.lang.Exception} into a failed {@link AppSearchResult} */ + @NonNull public <T> AppSearchResult<T> toAppSearchResult() { return AppSearchResult.newFailedResult(mResultCode, getMessage()); } diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp index c125f5686f0b..fc1d707edd86 100644 --- a/apex/appsearch/service/Android.bp +++ b/apex/appsearch/service/Android.bp @@ -20,7 +20,13 @@ java_library { "framework-appsearch", "services.core", ], - static_libs: ["icing-java-proto-lite"], + static_libs: [ + "icing-java-proto-lite", + "libicing-java", + ], + required: [ + "libicing", + ], jarjar_rules: "jarjar-rules.txt", apex_available: ["com.android.appsearch"], } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java index 16948b257392..75fad82d3fff 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -17,8 +17,10 @@ package com.android.server.appsearch; import android.annotation.NonNull; import android.app.appsearch.AppSearchBatchResult; +import android.app.appsearch.AppSearchDocument; import android.app.appsearch.AppSearchResult; import android.app.appsearch.IAppSearchManager; +import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; import android.os.Binder; import android.os.UserHandle; @@ -26,8 +28,7 @@ import android.os.UserHandle; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.Preconditions; import com.android.server.SystemService; -import com.android.server.appsearch.impl.AppSearchImpl; -import com.android.server.appsearch.impl.ImplInstanceManager; +import com.android.server.appsearch.external.localbackend.AppSearchImpl; import com.google.android.icing.proto.DocumentProto; import com.google.android.icing.proto.ResultSpecProto; @@ -44,6 +45,7 @@ import java.util.List; * TODO(b/142567528): add comments when implement this class */ public class AppSearchManagerService extends SystemService { + private static final String TAG = "AppSearchManagerService"; public AppSearchManagerService(Context context) { super(context); @@ -68,7 +70,8 @@ public class AppSearchManagerService extends SystemService { try { SchemaProto schema = SchemaProto.parseFrom(schemaBytes); AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); - impl.setSchema(callingUid, schema, forceOverride); + String databaseName = makeDatabaseName(callingUid); + impl.setSchema(databaseName, schema, forceOverride); callback.complete(AppSearchResult.newSuccessfulResult(/*value=*/ null)); } catch (Throwable t) { callback.complete(throwableToFailedResult(t)); @@ -88,13 +91,14 @@ public class AppSearchManagerService extends SystemService { long callingIdentity = Binder.clearCallingIdentity(); try { AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); + String databaseName = makeDatabaseName(callingUid); AppSearchBatchResult.Builder<String, Void> resultBuilder = new AppSearchBatchResult.Builder<>(); for (int i = 0; i < documentsBytes.size(); i++) { byte[] documentBytes = (byte[]) documentsBytes.get(i); DocumentProto document = DocumentProto.parseFrom(documentBytes); try { - impl.putDocument(callingUid, document); + impl.putDocument(databaseName, document); resultBuilder.setSuccess(document.getUri(), /*value=*/ null); } catch (Throwable t) { resultBuilder.setResult(document.getUri(), throwableToFailedResult(t)); @@ -118,12 +122,14 @@ public class AppSearchManagerService extends SystemService { long callingIdentity = Binder.clearCallingIdentity(); try { AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); + String databaseName = makeDatabaseName(callingUid); AppSearchBatchResult.Builder<String, byte[]> resultBuilder = new AppSearchBatchResult.Builder<>(); for (int i = 0; i < uris.size(); i++) { String uri = uris.get(i); try { - DocumentProto document = impl.getDocument(callingUid, uri); + DocumentProto document = impl.getDocument( + databaseName, AppSearchDocument.DEFAULT_NAMESPACE, uri); if (document == null) { resultBuilder.setFailure( uri, AppSearchResult.RESULT_NOT_FOUND, /*errorMessage=*/ null); @@ -161,8 +167,9 @@ public class AppSearchManagerService extends SystemService { ResultSpecProto resultSpecProto = ResultSpecProto.parseFrom(resultSpecBytes); ScoringSpecProto scoringSpecProto = ScoringSpecProto.parseFrom(scoringSpecBytes); AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); - SearchResultProto searchResultProto = - impl.query(callingUid, searchSpecProto, resultSpecProto, scoringSpecProto); + String databaseName = makeDatabaseName(callingUid); + SearchResultProto searchResultProto = impl.query( + databaseName, searchSpecProto, resultSpecProto, scoringSpecProto); // TODO(sidchhabra): Translate SearchResultProto errors into error codes. This might // better be done in AppSearchImpl by throwing an AppSearchException. if (searchResultProto.getStatus().getCode() != StatusProto.Code.OK) { @@ -190,17 +197,14 @@ public class AppSearchManagerService extends SystemService { long callingIdentity = Binder.clearCallingIdentity(); try { AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); + String databaseName = makeDatabaseName(callingUid); AppSearchBatchResult.Builder<String, Void> resultBuilder = new AppSearchBatchResult.Builder<>(); for (int i = 0; i < uris.size(); i++) { String uri = uris.get(i); try { - if (!impl.delete(callingUid, uri)) { - resultBuilder.setFailure( - uri, AppSearchResult.RESULT_NOT_FOUND, /*errorMessage=*/ null); - } else { - resultBuilder.setSuccess(uri, /*value= */null); - } + impl.remove(databaseName, AppSearchDocument.DEFAULT_NAMESPACE, uri); + resultBuilder.setSuccess(uri, /*value= */null); } catch (Throwable t) { resultBuilder.setResult(uri, throwableToFailedResult(t)); } @@ -223,19 +227,14 @@ public class AppSearchManagerService extends SystemService { long callingIdentity = Binder.clearCallingIdentity(); try { AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); + String databaseName = makeDatabaseName(callingUid); AppSearchBatchResult.Builder<String, Void> resultBuilder = new AppSearchBatchResult.Builder<>(); for (int i = 0; i < schemaTypes.size(); i++) { String schemaType = schemaTypes.get(i); try { - if (!impl.deleteByType(callingUid, schemaType)) { - resultBuilder.setFailure( - schemaType, - AppSearchResult.RESULT_NOT_FOUND, - /*errorMessage=*/ null); - } else { - resultBuilder.setSuccess(schemaType, /*value=*/ null); - } + impl.removeByType(databaseName, schemaType); + resultBuilder.setSuccess(schemaType, /*value=*/ null); } catch (Throwable t) { resultBuilder.setResult(schemaType, throwableToFailedResult(t)); } @@ -256,7 +255,8 @@ public class AppSearchManagerService extends SystemService { long callingIdentity = Binder.clearCallingIdentity(); try { AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); - impl.deleteAll(callingUid); + String databaseName = makeDatabaseName(callingUid); + impl.removeAll(databaseName); callback.complete(AppSearchResult.newSuccessfulResult(null)); } catch (Throwable t) { callback.complete(throwableToFailedResult(t)); @@ -265,6 +265,25 @@ public class AppSearchManagerService extends SystemService { } } + /** + * Returns a unique database name for the given uid. + * + * <p>The current implementation returns the package name of the app with this uid in a + * format like {@code com.example.package} or {@code com.example.sharedname:5678}. + */ + @NonNull + private String makeDatabaseName(int callingUid) { + // For regular apps, this call will return the package name. If callingUid is an + // android:sharedUserId, this value may be another type of name and have a :uid suffix. + String callingUidName = getContext().getPackageManager().getNameForUid(callingUid); + if (callingUidName == null) { + // Not sure how this is possible --- maybe app was uninstalled? + throw new IllegalStateException( + "Failed to look up package name for uid " + callingUid); + } + return callingUidName; + } + private <ValueType> AppSearchResult<ValueType> throwableToFailedResult( @NonNull Throwable t) { if (t instanceof AppSearchException) { diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java index 395e30e89dc0..c1e6b0fd205f 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java @@ -14,21 +14,32 @@ * limitations under the License. */ -package com.android.server.appsearch.impl; +package com.android.server.appsearch; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; +import android.os.Environment; +import android.os.storage.StorageManager; import android.util.SparseArray; +import com.android.server.appsearch.external.localbackend.AppSearchImpl; + +import java.io.File; + /** * Manages the lifecycle of instances of {@link AppSearchImpl}. * * <p>These instances are managed per unique device-user. */ public final class ImplInstanceManager { + private static final String APP_SEARCH_DIR = "appSearch"; + private static final SparseArray<AppSearchImpl> sInstances = new SparseArray<>(); + private ImplInstanceManager() {} + /** * Gets an instance of AppSearchImpl for the given user. * @@ -40,17 +51,33 @@ public final class ImplInstanceManager { * @return An initialized {@link AppSearchImpl} for this user */ @NonNull - public static AppSearchImpl getInstance(@NonNull Context context, @UserIdInt int userId) { + public static AppSearchImpl getInstance(@NonNull Context context, @UserIdInt int userId) + throws AppSearchException { AppSearchImpl instance = sInstances.get(userId); if (instance == null) { synchronized (ImplInstanceManager.class) { instance = sInstances.get(userId); if (instance == null) { - instance = new AppSearchImpl(context, userId); + instance = createImpl(context, userId); sInstances.put(userId, instance); } } } return instance; } + + private static AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId) + throws AppSearchException { + File appSearchDir = getAppSearchDir(context, userId); + AppSearchImpl appSearchImpl = new AppSearchImpl(appSearchDir); + appSearchImpl.initialize(); + return appSearchImpl; + } + + private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) { + // See com.android.internal.app.ChooserActivity::getPinnedSharedPrefs + File userCeDir = Environment.getDataUserCePackageDirectory( + StorageManager.UUID_PRIVATE_INTERNAL, userId, context.getPackageName()); + return new File(userCeDir, APP_SEARCH_DIR); + } } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/AppSearchImpl.java new file mode 100644 index 000000000000..462f45869b34 --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/AppSearchImpl.java @@ -0,0 +1,865 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appsearch.external.localbackend; + +import android.util.Log; + +import android.annotation.AnyThread; +import com.android.internal.annotations.GuardedBy; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import android.annotation.WorkerThread; +import android.app.appsearch.AppSearchResult; +import android.app.appsearch.exceptions.AppSearchException; + +import com.google.android.icing.IcingSearchEngine; +import com.google.android.icing.proto.DeleteByNamespaceResultProto; +import com.google.android.icing.proto.DeleteBySchemaTypeResultProto; +import com.google.android.icing.proto.DeleteResultProto; +import com.google.android.icing.proto.DocumentProto; +import com.google.android.icing.proto.GetAllNamespacesResultProto; +import com.google.android.icing.proto.GetOptimizeInfoResultProto; +import com.google.android.icing.proto.GetResultProto; +import com.google.android.icing.proto.GetSchemaResultProto; +import com.google.android.icing.proto.IcingSearchEngineOptions; +import com.google.android.icing.proto.InitializeResultProto; +import com.google.android.icing.proto.OptimizeResultProto; +import com.google.android.icing.proto.PropertyConfigProto; +import com.google.android.icing.proto.PropertyProto; +import com.google.android.icing.proto.PutResultProto; +import com.google.android.icing.proto.ResetResultProto; +import com.google.android.icing.proto.ResultSpecProto; +import com.google.android.icing.proto.SchemaProto; +import com.google.android.icing.proto.SchemaTypeConfigProto; +import com.google.android.icing.proto.ScoringSpecProto; +import com.google.android.icing.proto.SearchResultProto; +import com.google.android.icing.proto.SearchSpecProto; +import com.google.android.icing.proto.SetSchemaResultProto; +import com.google.android.icing.proto.StatusProto; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Manages interaction with the native IcingSearchEngine and other components to implement AppSearch + * functionality. + * + * <p>Callers should call {@link #initialize} before using the AppSearchImpl instance. Never create + * two instances using the same folder. + * + * <p>A single instance of {@link AppSearchImpl} can support all databases. Schemas and documents + * are physically saved together in {@link IcingSearchEngine}, but logically isolated: + * <ul> + * <li>Rewrite SchemaType in SchemaProto by adding database name prefix and save into + * SchemaTypes set in {@link #setSchema}. + * <li>Rewrite namespace and SchemaType in DocumentProto by adding database name prefix and + * save to namespaces set in {@link #putDocument}. + * <li>Remove database name prefix when retrieve documents in {@link #getDocument} and + * {@link #query}. + * <li>Rewrite filters in {@link SearchSpecProto} to have all namespaces and schema types of + * the queried database when user using empty filters in {@link #query}. + * </ul> + * + * <p>Methods in this class belong to two groups, the query group and the mutate group. + * <ul> + * <li>All methods are going to modify global parameters and data in Icing are executed under + * WRITE lock to keep thread safety. + * <li>All methods are going to access global parameters or query data from Icing are executed + * under READ lock to improve query performance. + * </ul> + * + * <p>This class is thread safe. + * @hide + */ + +@WorkerThread +public final class AppSearchImpl { + private static final String TAG = "AppSearchImpl"; + + @VisibleForTesting + static final int OPTIMIZE_THRESHOLD_DOC_COUNT = 1000; + @VisibleForTesting + static final int OPTIMIZE_THRESHOLD_BYTES = 1_000_000; // 1MB + @VisibleForTesting + static final int CHECK_OPTIMIZE_INTERVAL = 100; + + private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(); + private final CountDownLatch mInitCompleteLatch = new CountDownLatch(1); + private final File mIcingDir; + private IcingSearchEngine mIcingSearchEngine; + + // The map contains schemaTypes and namespaces for all database. All values in the map have + // been already added database name prefix. + private final Map<String, Set<String>> mSchemaMap = new HashMap<>(); + private final Map<String, Set<String>> mNamespaceMap = new HashMap<>(); + + /** + * The counter to check when to call {@link #checkForOptimize(boolean)}. The interval is + * {@link #CHECK_OPTIMIZE_INTERVAL}. + */ + private int mOptimizeIntervalCount = 0; + + /** Creates an instance of {@link AppSearchImpl} which writes data to the given folder. */ + @AnyThread + public AppSearchImpl(@NonNull File icingDir) { + mIcingDir = icingDir; + } + + /** + * Initializes the underlying IcingSearchEngine. + * + * <p>This method belongs to mutate group. + * + * @throws AppSearchException on IcingSearchEngine error. + */ + public void initialize() throws AppSearchException { + if (isInitialized()) { + return; + } + boolean isReset = false; + mReadWriteLock.writeLock().lock(); + try { + // We synchronize here because we don't want to call IcingSearchEngine.initialize() more + // than once. It's unnecessary and can be a costly operation. + if (isInitialized()) { + return; + } + IcingSearchEngineOptions options = IcingSearchEngineOptions.newBuilder() + .setBaseDir(mIcingDir.getAbsolutePath()).build(); + mIcingSearchEngine = new IcingSearchEngine(options); + + InitializeResultProto initializeResultProto = mIcingSearchEngine.initialize(); + SchemaProto schemaProto = null; + GetAllNamespacesResultProto getAllNamespacesResultProto = null; + try { + checkSuccess(initializeResultProto.getStatus()); + schemaProto = getSchemaProto(); + getAllNamespacesResultProto = mIcingSearchEngine.getAllNamespaces(); + checkSuccess(getAllNamespacesResultProto.getStatus()); + } catch (AppSearchException e) { + // Some error. Reset and see if it fixes it. + reset(); + isReset = true; + } + for (SchemaTypeConfigProto schema : schemaProto.getTypesList()) { + String qualifiedSchemaType = schema.getSchemaType(); + addToMap(mSchemaMap, getDatabaseName(qualifiedSchemaType), qualifiedSchemaType); + } + for (String qualifiedNamespace : getAllNamespacesResultProto.getNamespacesList()) { + addToMap(mNamespaceMap, getDatabaseName(qualifiedNamespace), qualifiedNamespace); + } + mInitCompleteLatch.countDown(); + if (!isReset) { + checkForOptimize(/* force= */ true); + } + } finally { + mReadWriteLock.writeLock().unlock(); + } + } + + /** Checks if the internal state of {@link AppSearchImpl} has been initialized. */ + @AnyThread + public boolean isInitialized() { + return mInitCompleteLatch.getCount() == 0; + } + + /** + * Updates the AppSearch schema for this app. + * + * <p>This method belongs to mutate group. + * + * @param databaseName The name of the database where this schema lives. + * @param origSchema The schema to set for this app. + * @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents + * which do not comply with the new schema will be deleted. + * @throws AppSearchException on IcingSearchEngine error. + * @throws InterruptedException if the current thread was interrupted during execution. + */ + public void setSchema(@NonNull String databaseName, @NonNull SchemaProto origSchema, + boolean forceOverride) throws AppSearchException, InterruptedException { + awaitInitialized(); + + SchemaProto schemaProto = getSchemaProto(); + + SchemaProto.Builder existingSchemaBuilder = schemaProto.toBuilder(); + + // Combine the existing schema (which may have types from other databases) with this + // database's new schema. Modifies the existingSchemaBuilder. + Set<String> newTypeNames = rewriteSchema(databaseName, existingSchemaBuilder, origSchema); + + SetSchemaResultProto setSchemaResultProto; + mReadWriteLock.writeLock().lock(); + try { + setSchemaResultProto = mIcingSearchEngine.setSchema(existingSchemaBuilder.build(), + forceOverride); + checkSuccess(setSchemaResultProto.getStatus()); + mSchemaMap.put(databaseName, newTypeNames); + if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0 + || (setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0 + && forceOverride)) { + // Any existing schemas which is not in origSchema will be deleted, and all + // documents of these types were also deleted. And so well if we force override + // incompatible schemas. + checkForOptimize(/* force= */true); + } + } finally { + mReadWriteLock.writeLock().unlock(); + } + } + + /** + * Adds a document to the AppSearch index. + * + * <p>This method belongs to mutate group. + * + * @param databaseName The databaseName this document resides in. + * @param document The document to index. + * @throws AppSearchException on IcingSearchEngine error. + * @throws InterruptedException if the current thread was interrupted during execution. + */ + public void putDocument(@NonNull String databaseName, @NonNull DocumentProto document) + throws AppSearchException, InterruptedException { + awaitInitialized(); + + DocumentProto.Builder documentBuilder = document.toBuilder(); + rewriteDocumentTypes(getDatabasePrefix(databaseName), documentBuilder, /*add=*/ true); + + PutResultProto putResultProto; + mReadWriteLock.writeLock().lock(); + try { + putResultProto = mIcingSearchEngine.put(documentBuilder.build()); + addToMap(mNamespaceMap, databaseName, documentBuilder.getNamespace()); + // The existing documents with same URI will be deleted, so there maybe some resources + // could be released after optimize(). + checkForOptimize(/* force= */false); + } finally { + mReadWriteLock.writeLock().unlock(); + } + checkSuccess(putResultProto.getStatus()); + } + + /** + * Retrieves a document from the AppSearch index by URI. + * + * <p>This method belongs to query group. + * + * @param databaseName The databaseName this document resides in. + * @param namespace The namespace this document resides in. + * @param uri The URI of the document to get. + * @return The Document contents, or {@code null} if no such URI exists in the system. + * @throws AppSearchException on IcingSearchEngine error. + * @throws InterruptedException if the current thread was interrupted during execution. + */ + @Nullable + public DocumentProto getDocument(@NonNull String databaseName, @NonNull String namespace, + @NonNull String uri) throws AppSearchException, InterruptedException { + awaitInitialized(); + GetResultProto getResultProto; + mReadWriteLock.readLock().lock(); + try { + getResultProto = mIcingSearchEngine.get( + getDatabasePrefix(databaseName) + namespace, uri); + } finally { + mReadWriteLock.readLock().unlock(); + } + checkSuccess(getResultProto.getStatus()); + + DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder(); + rewriteDocumentTypes(getDatabasePrefix(databaseName), documentBuilder, /*add=*/ false); + return documentBuilder.build(); + } + + /** + * Executes a query against the AppSearch index and returns results. + * + * <p>This method belongs to query group. + * + * @param databaseName The databaseName this query for. + * @param searchSpec Defines what and how to search + * @param resultSpec Defines what results to show + * @param scoringSpec Defines how to order results + * @return The results of performing this search The proto might have no {@code results} if no + * documents matched the query. + * @throws AppSearchException on IcingSearchEngine error. + * @throws InterruptedException if the current thread was interrupted during execution. + */ + @NonNull + public SearchResultProto query( + @NonNull String databaseName, + @NonNull SearchSpecProto searchSpec, + @NonNull ResultSpecProto resultSpec, + @NonNull ScoringSpecProto scoringSpec) throws AppSearchException, InterruptedException { + awaitInitialized(); + + SearchSpecProto.Builder searchSpecBuilder = searchSpec.toBuilder(); + SearchResultProto searchResultProto; + mReadWriteLock.readLock().lock(); + try { + // Only rewrite SearchSpec for non empty database. + // rewriteSearchSpecForNonEmptyDatabase will return false for empty database, we + // should just return an empty SearchResult and skip sending request to Icing. + if (!rewriteSearchSpecForNonEmptyDatabase(databaseName, searchSpecBuilder)) { + return SearchResultProto.newBuilder() + .setStatus(StatusProto.newBuilder() + .setCode(StatusProto.Code.OK) + .build()) + .build(); + } + searchResultProto = mIcingSearchEngine.search( + searchSpecBuilder.build(), scoringSpec, resultSpec); + } finally { + mReadWriteLock.readLock().unlock(); + } + checkSuccess(searchResultProto.getStatus()); + if (searchResultProto.getResultsCount() == 0) { + return searchResultProto; + } + return rewriteSearchResultProto(databaseName, searchResultProto); + } + + /** + * Fetches the next page of results of a previously executed query. Results can be empty if + * next-page token is invalid or all pages have been returned. + * + * @param databaseName The databaseName of the previously executed query. + * @param nextPageToken The token of pre-loaded results of previously executed query. + * @return The next page of results of previously executed query. + * @throws AppSearchException on IcingSearchEngine error. + * @throws InterruptedException if the current thread was interrupted during execution. + */ + @NonNull + public SearchResultProto getNextPage(@NonNull String databaseName, long nextPageToken) + throws AppSearchException, InterruptedException { + awaitInitialized(); + + SearchResultProto searchResultProto = mIcingSearchEngine.getNextPage(nextPageToken); + checkSuccess(searchResultProto.getStatus()); + if (searchResultProto.getResultsCount() == 0) { + return searchResultProto; + } + return rewriteSearchResultProto(databaseName, searchResultProto); + } + + /** + * Invalidates the next-page token so that no more results of the related query can be returned. + * @param nextPageToken The token of pre-loaded results of previously executed query to be + * Invalidated. + */ + public void invalidateNextPageToken(long nextPageToken) throws InterruptedException { + awaitInitialized(); + mIcingSearchEngine.invalidateNextPageToken(nextPageToken); + } + + /** + * Removes the given document by URI. + * + * <p>This method belongs to mutate group. + * + * @param databaseName The databaseName the document is in. + * @param namespace Namespace of the document to remove. + * @param uri URI of the document to remove. + * @throws AppSearchException on IcingSearchEngine error. + * @throws InterruptedException if the current thread was interrupted during execution. + */ + public void remove(@NonNull String databaseName, @NonNull String namespace, + @NonNull String uri) throws AppSearchException, InterruptedException { + awaitInitialized(); + + String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace; + DeleteResultProto deleteResultProto; + mReadWriteLock.writeLock().lock(); + try { + deleteResultProto = mIcingSearchEngine.delete(qualifiedNamespace, uri); + checkForOptimize(/* force= */false); + } finally { + mReadWriteLock.writeLock().unlock(); + } + checkSuccess(deleteResultProto.getStatus()); + } + + /** + * Removes all documents having the given {@code schemaType} in given database. + * + * <p>This method belongs to mutate group. + * + * @param databaseName The databaseName that contains documents of schemaType. + * @param schemaType The schemaType of documents to remove. + * @throws AppSearchException on IcingSearchEngine error. + * @throws InterruptedException if the current thread was interrupted during execution. + */ + public void removeByType(@NonNull String databaseName, @NonNull String schemaType) + throws AppSearchException, InterruptedException { + awaitInitialized(); + + String qualifiedType = getDatabasePrefix(databaseName) + schemaType; + DeleteBySchemaTypeResultProto deleteBySchemaTypeResultProto; + mReadWriteLock.writeLock().lock(); + try { + Set<String> existingSchemaTypes = mSchemaMap.get(databaseName); + if (existingSchemaTypes == null || !existingSchemaTypes.contains(qualifiedType)) { + return; + } + deleteBySchemaTypeResultProto = mIcingSearchEngine.deleteBySchemaType(qualifiedType); + checkForOptimize(/* force= */true); + } finally { + mReadWriteLock.writeLock().unlock(); + } + checkSuccess(deleteBySchemaTypeResultProto.getStatus()); + } + + /** + * Removes all documents having the given {@code namespace} in given database. + * + * <p>This method belongs to mutate group. + * + * @param databaseName The databaseName that contains documents of namespace. + * @param namespace The namespace of documents to remove. + * @throws AppSearchException on IcingSearchEngine error. + * @throws InterruptedException if the current thread was interrupted during execution. + */ + public void removeByNamespace(@NonNull String databaseName, @NonNull String namespace) + throws AppSearchException, InterruptedException { + awaitInitialized(); + + String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace; + DeleteByNamespaceResultProto deleteByNamespaceResultProto; + mReadWriteLock.writeLock().lock(); + try { + Set<String> existingNamespaces = mNamespaceMap.get(databaseName); + if (existingNamespaces == null || !existingNamespaces.contains(qualifiedNamespace)) { + return; + } + deleteByNamespaceResultProto = mIcingSearchEngine.deleteByNamespace(qualifiedNamespace); + checkForOptimize(/* force= */true); + } finally { + mReadWriteLock.writeLock().unlock(); + } + checkSuccess(deleteByNamespaceResultProto.getStatus()); + } + + /** + * Clears the given database by removing all documents and types. + * + * <p>The schemas will remain. To clear everything including schemas, please call + * {@link #setSchema} with an empty schema and {@code forceOverride} set to true. + * + * <p>This method belongs to mutate group. + * + * @param databaseName The databaseName to remove all documents from. + * @throws AppSearchException on IcingSearchEngine error. + * @throws InterruptedException if the current thread was interrupted during execution. + */ + public void removeAll(@NonNull String databaseName) + throws AppSearchException, InterruptedException { + awaitInitialized(); + mReadWriteLock.writeLock().lock(); + try { + Set<String> existingNamespaces = mNamespaceMap.get(databaseName); + if (existingNamespaces == null) { + return; + } + for (String namespace : existingNamespaces) { + DeleteByNamespaceResultProto deleteByNamespaceResultProto = + mIcingSearchEngine.deleteByNamespace(namespace); + // There's no way for AppSearch to know that all documents in a particular + // namespace have been deleted, but if you try to delete an empty namespace, Icing + // returns NOT_FOUND. Just ignore that code. + checkCodeOneOf( + deleteByNamespaceResultProto.getStatus(), + StatusProto.Code.OK, StatusProto.Code.NOT_FOUND); + } + mNamespaceMap.remove(databaseName); + checkForOptimize(/* force= */true); + } finally { + mReadWriteLock.writeLock().unlock(); + } + } + + /** + * Clears documents and schema across all databaseNames. + * + * <p>This method belongs to mutate group. + * + * @throws AppSearchException on IcingSearchEngine error. + */ + @VisibleForTesting + public void reset() throws AppSearchException { + ResetResultProto resetResultProto; + mReadWriteLock.writeLock().lock(); + try { + resetResultProto = mIcingSearchEngine.reset(); + mOptimizeIntervalCount = 0; + mSchemaMap.clear(); + mNamespaceMap.clear(); + } finally { + mReadWriteLock.writeLock().unlock(); + } + checkSuccess(resetResultProto.getStatus()); + } + + /** + * Rewrites all types mentioned in the given {@code newSchema} to prepend {@code prefix}. + * Rewritten types will be added to the {@code existingSchema}. + * + * @param databaseName The name of the database where this schema lives. + * @param existingSchema A schema that may contain existing types from across all database + * instances. Will be mutated to contain the properly rewritten schema + * types from {@code newSchema}. + * @param newSchema Schema with types to add to the {@code existingSchema}. + * @return a Set contains all remaining qualified schema type names in given database. + */ + @VisibleForTesting + Set<String> rewriteSchema(@NonNull String databaseName, + @NonNull SchemaProto.Builder existingSchema, + @NonNull SchemaProto newSchema) throws AppSearchException { + String prefix = getDatabasePrefix(databaseName); + HashMap<String, SchemaTypeConfigProto> newTypesToProto = new HashMap<>(); + // Rewrite the schema type to include the typePrefix. + for (int typeIdx = 0; typeIdx < newSchema.getTypesCount(); typeIdx++) { + SchemaTypeConfigProto.Builder typeConfigBuilder = + newSchema.getTypes(typeIdx).toBuilder(); + + // Rewrite SchemaProto.types.schema_type + String newSchemaType = prefix + typeConfigBuilder.getSchemaType(); + typeConfigBuilder.setSchemaType(newSchemaType); + + // Rewrite SchemaProto.types.properties.schema_type + for (int propertyIdx = 0; + propertyIdx < typeConfigBuilder.getPropertiesCount(); + propertyIdx++) { + PropertyConfigProto.Builder propertyConfigBuilder = + typeConfigBuilder.getProperties(propertyIdx).toBuilder(); + if (!propertyConfigBuilder.getSchemaType().isEmpty()) { + String newPropertySchemaType = + prefix + propertyConfigBuilder.getSchemaType(); + propertyConfigBuilder.setSchemaType(newPropertySchemaType); + typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder); + } + } + + newTypesToProto.put(newSchemaType, typeConfigBuilder.build()); + } + + Set<String> newSchemaTypesName = newTypesToProto.keySet(); + + // Combine the existing schema (which may have types from other databases) with this + // database's new schema. Modifies the existingSchemaBuilder. + // Check if we need to replace any old schema types with the new ones. + for (int i = 0; i < existingSchema.getTypesCount(); i++) { + String schemaType = existingSchema.getTypes(i).getSchemaType(); + SchemaTypeConfigProto newProto = newTypesToProto.remove(schemaType); + if (newProto != null) { + // Replacement + existingSchema.setTypes(i, newProto); + } else if (databaseName.equals(getDatabaseName(schemaType))) { + // All types existing before but not in newSchema should be removed. + existingSchema.removeTypes(i); + --i; + } + } + // We've been removing existing types from newTypesToProto, so everything that remains is + // new. + existingSchema.addAllTypes(newTypesToProto.values()); + + return newSchemaTypesName; + } + + /** + * Rewrites all types and namespaces mentioned anywhere in {@code documentBuilder} to prepend + * or remove {@code prefix}. + * + * @param prefix The prefix to add or remove + * @param documentBuilder The document to mutate + * @param add Whether to add prefix to the types and namespaces. If {@code false}, + * prefix will be removed. + * @throws IllegalStateException If {@code add=false} and the document has a type or namespace + * that doesn't start with {@code prefix}. + */ + @VisibleForTesting + void rewriteDocumentTypes( + @NonNull String prefix, + @NonNull DocumentProto.Builder documentBuilder, + boolean add) { + // Rewrite the type name to include/remove the prefix. + String newSchema; + if (add) { + newSchema = prefix + documentBuilder.getSchema(); + } else { + newSchema = removePrefix(prefix, "schemaType", documentBuilder.getSchema()); + } + documentBuilder.setSchema(newSchema); + + // Rewrite the namespace to include/remove the prefix. + if (add) { + documentBuilder.setNamespace(prefix + documentBuilder.getNamespace()); + } else { + documentBuilder.setNamespace( + removePrefix(prefix, "namespace", documentBuilder.getNamespace())); + } + + // Recurse into derived documents + for (int propertyIdx = 0; + propertyIdx < documentBuilder.getPropertiesCount(); + propertyIdx++) { + int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount(); + if (documentCount > 0) { + PropertyProto.Builder propertyBuilder = + documentBuilder.getProperties(propertyIdx).toBuilder(); + for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) { + DocumentProto.Builder derivedDocumentBuilder = + propertyBuilder.getDocumentValues(documentIdx).toBuilder(); + rewriteDocumentTypes(prefix, derivedDocumentBuilder, add); + propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder); + } + documentBuilder.setProperties(propertyIdx, propertyBuilder); + } + } + } + + /** + * Rewrites searchSpec by adding schemaTypeFilter and namespacesFilter + * + * <p>If user input empty filter lists, will look up {@link #mSchemaMap} and + * {@link #mNamespaceMap} and put all values belong to current database to narrow down Icing + * search area. + * <p>This method should be only called in query methods and get the READ lock to keep thread + * safety. + * @return false if the current database is brand new and contains nothing. We should just + * return an empty query result to user. + */ + @VisibleForTesting + @GuardedBy("mReadWriteLock") + boolean rewriteSearchSpecForNonEmptyDatabase(@NonNull String databaseName, + @NonNull SearchSpecProto.Builder searchSpecBuilder) { + Set<String> existingSchemaTypes = mSchemaMap.get(databaseName); + Set<String> existingNamespaces = mNamespaceMap.get(databaseName); + if (existingSchemaTypes == null || existingSchemaTypes.isEmpty() + || existingNamespaces == null || existingNamespaces.isEmpty()) { + return false; + } + // Rewrite any existing schema types specified in the searchSpec, or add schema types to + // limit the search to this database instance. + if (searchSpecBuilder.getSchemaTypeFiltersCount() > 0) { + for (int i = 0; i < searchSpecBuilder.getSchemaTypeFiltersCount(); i++) { + String qualifiedType = getDatabasePrefix(databaseName) + + searchSpecBuilder.getSchemaTypeFilters(i); + if (existingSchemaTypes.contains(qualifiedType)) { + searchSpecBuilder.setSchemaTypeFilters(i, qualifiedType); + } + } + } else { + searchSpecBuilder.addAllSchemaTypeFilters(existingSchemaTypes); + } + + // Rewrite any existing namespaces specified in the searchSpec, or add namespaces to + // limit the search to this database instance. + if (searchSpecBuilder.getNamespaceFiltersCount() > 0) { + for (int i = 0; i < searchSpecBuilder.getNamespaceFiltersCount(); i++) { + String qualifiedNamespace = getDatabasePrefix(databaseName) + + searchSpecBuilder.getNamespaceFilters(i); + searchSpecBuilder.setNamespaceFilters(i, qualifiedNamespace); + } + } else { + searchSpecBuilder.addAllNamespaceFilters(existingNamespaces); + } + return true; + } + + @VisibleForTesting + SchemaProto getSchemaProto() throws AppSearchException { + GetSchemaResultProto schemaProto = mIcingSearchEngine.getSchema(); + // TODO(b/161935693) check GetSchemaResultProto is success or not. Call reset() if it's not. + // TODO(b/161935693) only allow GetSchemaResultProto NOT_FOUND on first run + checkCodeOneOf(schemaProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND); + return schemaProto.getSchema(); + } + + @NonNull + private String getDatabasePrefix(@NonNull String databaseName) { + return databaseName + "/"; + } + + @NonNull + private String getDatabaseName(@NonNull String prefixedValue) throws AppSearchException { + int delimiterIndex = prefixedValue.indexOf('/'); + if (delimiterIndex == -1) { + throw new AppSearchException(AppSearchResult.RESULT_UNKNOWN_ERROR, + "The databaseName prefixed value doesn't contains a valid database name."); + } + return prefixedValue.substring(0, delimiterIndex); + } + + @NonNull + private static String removePrefix(@NonNull String prefix, @NonNull String inputType, + @NonNull String input) { + if (!input.startsWith(prefix)) { + throw new IllegalStateException( + "Unexpected " + inputType + " \"" + input + + "\" does not start with \"" + prefix + "\""); + } + return input.substring(prefix.length()); + } + + @GuardedBy("mReadWriteLock") + private void addToMap(Map<String, Set<String>> map, String databaseName, String prefixedValue) { + Set<String> values = map.get(databaseName); + if (values == null) { + values = new HashSet<>(); + map.put(databaseName, values); + } + values.add(prefixedValue); + } + + /** + * Waits for the instance to become initialized. + * + * @throws InterruptedException if the current thread was interrupted during waiting. + */ + private void awaitInitialized() throws InterruptedException { + mInitCompleteLatch.await(); + } + + /** + * Checks the given status code and throws an {@link AppSearchException} if code is an error. + * + * @throws AppSearchException on error codes. + */ + private void checkSuccess(StatusProto statusProto) throws AppSearchException { + checkCodeOneOf(statusProto, StatusProto.Code.OK); + } + + /** + * Checks the given status code is one of the provided codes, and throws an + * {@link AppSearchException} if it is not. + */ + private void checkCodeOneOf(StatusProto statusProto, StatusProto.Code... codes) + throws AppSearchException { + for (int i = 0; i < codes.length; i++) { + if (codes[i] == statusProto.getCode()) { + // Everything's good + return; + } + } + + if (statusProto.getCode() == StatusProto.Code.WARNING_DATA_LOSS) { + // TODO: May want to propagate WARNING_DATA_LOSS up to AppSearchManager so they can + // choose to log the error or potentially pass it on to clients. + Log.w(TAG, "Encountered WARNING_DATA_LOSS: " + statusProto.getMessage()); + return; + } + + throw statusProtoToAppSearchException(statusProto); + } + + /** + * Checks whether {@link IcingSearchEngine#optimize()} should be called to release resources. + * + * <p>This method should be only called in mutate methods and get the WRITE lock to keep thread + * safety. + * <p>{@link IcingSearchEngine#optimize()} should be called only if + * {@link GetOptimizeInfoResultProto} shows there is enough resources could be released. + * <p>{@link IcingSearchEngine#getOptimizeInfo()} should be called once per + * {@link #CHECK_OPTIMIZE_INTERVAL} of remove executions. + * + * @param force whether we should directly call {@link IcingSearchEngine#getOptimizeInfo()}. + */ + @GuardedBy("mReadWriteLock") + private void checkForOptimize(boolean force) throws AppSearchException { + ++mOptimizeIntervalCount; + if (force || mOptimizeIntervalCount >= CHECK_OPTIMIZE_INTERVAL) { + mOptimizeIntervalCount = 0; + GetOptimizeInfoResultProto optimizeInfo = getOptimizeInfoResult(); + checkSuccess(optimizeInfo.getStatus()); + // Second threshold, decide when to call optimize(). + if (optimizeInfo.getOptimizableDocs() >= OPTIMIZE_THRESHOLD_DOC_COUNT + || optimizeInfo.getEstimatedOptimizableBytes() + >= OPTIMIZE_THRESHOLD_BYTES) { + // TODO(b/155939114): call optimize in the same thread will slow down api calls + // significantly. Move this call to background. + OptimizeResultProto optimizeResultProto = mIcingSearchEngine.optimize(); + checkSuccess(optimizeResultProto.getStatus()); + } + // TODO(b/147699081): Return OptimizeResultProto & log lost data detail once we add + // a field to indicate lost_schema and lost_documents in OptimizeResultProto. + // go/icing-library-apis. + } + } + + /** Remove the rewritten schema types from any result documents.*/ + private SearchResultProto rewriteSearchResultProto(@NonNull String databaseName, + @NonNull SearchResultProto searchResultProto) { + SearchResultProto.Builder searchResultsBuilder = searchResultProto.toBuilder(); + for (int i = 0; i < searchResultsBuilder.getResultsCount(); i++) { + if (searchResultProto.getResults(i).hasDocument()) { + SearchResultProto.ResultProto.Builder resultBuilder = + searchResultsBuilder.getResults(i).toBuilder(); + DocumentProto.Builder documentBuilder = resultBuilder.getDocument().toBuilder(); + rewriteDocumentTypes( + getDatabasePrefix(databaseName), documentBuilder, /*add=*/false); + resultBuilder.setDocument(documentBuilder); + searchResultsBuilder.setResults(i, resultBuilder); + } + } + return searchResultsBuilder.build(); + } + + @VisibleForTesting + GetOptimizeInfoResultProto getOptimizeInfoResult() { + return mIcingSearchEngine.getOptimizeInfo(); + } + + /** + * Converts an erroneous status code to an AppSearchException. Callers should ensure that + * the status code is not OK or WARNING_DATA_LOSS. + * + * @param statusProto StatusProto with error code and message to translate into + * AppSearchException. + * @return AppSearchException with the parallel error code. + */ + private AppSearchException statusProtoToAppSearchException(StatusProto statusProto) { + switch (statusProto.getCode()) { + case INVALID_ARGUMENT: + return new AppSearchException(AppSearchResult.RESULT_INVALID_ARGUMENT, + statusProto.getMessage()); + case NOT_FOUND: + return new AppSearchException(AppSearchResult.RESULT_NOT_FOUND, + statusProto.getMessage()); + case FAILED_PRECONDITION: + // Fallthrough + case ABORTED: + // Fallthrough + case INTERNAL: + return new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR, + statusProto.getMessage()); + case OUT_OF_SPACE: + return new AppSearchException(AppSearchResult.RESULT_OUT_OF_SPACE, + statusProto.getMessage()); + default: + // Some unknown/unsupported error + return new AppSearchException(AppSearchResult.RESULT_UNKNOWN_ERROR, + "Unknown IcingSearchEngine status code: " + statusProto.getCode()); + } + } +} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java deleted file mode 100644 index 4358d2086181..000000000000 --- a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.appsearch.impl; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.content.Context; -import android.util.ArraySet; - -import com.android.internal.annotations.VisibleForTesting; - -import com.google.android.icing.proto.DocumentProto; -import com.google.android.icing.proto.PropertyConfigProto; -import com.google.android.icing.proto.PropertyProto; -import com.google.android.icing.proto.ResultSpecProto; -import com.google.android.icing.proto.SchemaProto; -import com.google.android.icing.proto.SchemaTypeConfigProto; -import com.google.android.icing.proto.ScoringSpecProto; -import com.google.android.icing.proto.SearchResultProto; -import com.google.android.icing.proto.SearchSpecProto; - -import java.util.Set; - -/** - * Manages interaction with {@link FakeIcing} and other components to implement AppSearch - * functionality. - */ -public final class AppSearchImpl { - private final Context mContext; - private final @UserIdInt int mUserId; - private final FakeIcing mFakeIcing = new FakeIcing(); - - AppSearchImpl(@NonNull Context context, @UserIdInt int userId) { - mContext = context; - mUserId = userId; - } - - /** - * Updates the AppSearch schema for this app. - * - * @param callingUid The uid of the app calling AppSearch. - * @param origSchema The schema to set for this app. - * @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents - * which do not comply with the new schema will be deleted. - */ - public void setSchema(int callingUid, @NonNull SchemaProto origSchema, boolean forceOverride) { - // Rewrite schema type names to include the calling app's package and uid. - String typePrefix = getTypePrefix(callingUid); - SchemaProto.Builder schemaBuilder = origSchema.toBuilder(); - rewriteSchemaTypes(typePrefix, schemaBuilder); - - // TODO(b/145635424): Save in schema type map - // TODO(b/145635424): Apply the schema to Icing and report results - } - - /** - * Rewrites all types mentioned in the given {@code schemaBuilder} to prepend - * {@code typePrefix}. - * - * @param typePrefix The prefix to add - * @param schemaBuilder The schema to mutate - */ - @VisibleForTesting - void rewriteSchemaTypes( - @NonNull String typePrefix, @NonNull SchemaProto.Builder schemaBuilder) { - for (int typeIdx = 0; typeIdx < schemaBuilder.getTypesCount(); typeIdx++) { - SchemaTypeConfigProto.Builder typeConfigBuilder = - schemaBuilder.getTypes(typeIdx).toBuilder(); - - // Rewrite SchemaProto.types.schema_type - String newSchemaType = typePrefix + typeConfigBuilder.getSchemaType(); - typeConfigBuilder.setSchemaType(newSchemaType); - - // Rewrite SchemaProto.types.properties.schema_type - for (int propertyIdx = 0; - propertyIdx < typeConfigBuilder.getPropertiesCount(); - propertyIdx++) { - PropertyConfigProto.Builder propertyConfigBuilder = - typeConfigBuilder.getProperties(propertyIdx).toBuilder(); - if (!propertyConfigBuilder.getSchemaType().isEmpty()) { - String newPropertySchemaType = - typePrefix + propertyConfigBuilder.getSchemaType(); - propertyConfigBuilder.setSchemaType(newPropertySchemaType); - typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder); - } - } - - schemaBuilder.setTypes(typeIdx, typeConfigBuilder); - } - } - - /** - * Adds a document to the AppSearch index. - * - * @param callingUid The uid of the app calling AppSearch. - * @param origDocument The document to index. - */ - public void putDocument(int callingUid, @NonNull DocumentProto origDocument) { - // Rewrite the type names to include the app's prefix - String typePrefix = getTypePrefix(callingUid); - DocumentProto.Builder documentBuilder = origDocument.toBuilder(); - rewriteDocumentTypes(typePrefix, documentBuilder, /*add=*/ true); - mFakeIcing.put(documentBuilder.build()); - } - - /** - * Retrieves a document from the AppSearch index by URI. - * - * @param callingUid The uid of the app calling AppSearch. - * @param uri The URI of the document to get. - * @return The Document contents, or {@code null} if no such URI exists in the system. - */ - @Nullable - public DocumentProto getDocument(int callingUid, @NonNull String uri) { - String typePrefix = getTypePrefix(callingUid); - DocumentProto document = mFakeIcing.get(uri); - if (document == null) { - return null; - } - - // TODO(b/146526096): Since FakeIcing doesn't currently handle namespaces, we perform a - // post-filter to make sure we don't return documents we shouldn't. This should be removed - // once the real Icing Lib is implemented. - if (!document.getNamespace().equals(typePrefix)) { - return null; - } - - // Rewrite the type names to remove the app's prefix - DocumentProto.Builder documentBuilder = document.toBuilder(); - rewriteDocumentTypes(typePrefix, documentBuilder, /*add=*/ false); - return documentBuilder.build(); - } - - /** - * Executes a query against the AppSearch index and returns results. - * - * @param callingUid The uid of the app calling AppSearch. - * @param searchSpec Defines what and how to search - * @param resultSpec Defines what results to show - * @param scoringSpec Defines how to order results - * @return The results of performing this search The proto might have no {@code results} if no - * documents matched the query. - */ - @NonNull - public SearchResultProto query( - int callingUid, - @NonNull SearchSpecProto searchSpec, - @NonNull ResultSpecProto resultSpec, - @NonNull ScoringSpecProto scoringSpec) { - String typePrefix = getTypePrefix(callingUid); - SearchResultProto searchResults = mFakeIcing.query(searchSpec.getQuery()); - if (searchResults.getResultsCount() == 0) { - return searchResults; - } - Set<String> qualifiedSearchFilters = null; - if (searchSpec.getSchemaTypeFiltersCount() > 0) { - qualifiedSearchFilters = new ArraySet<>(searchSpec.getSchemaTypeFiltersCount()); - for (String schema : searchSpec.getSchemaTypeFiltersList()) { - String qualifiedSchema = typePrefix + schema; - qualifiedSearchFilters.add(qualifiedSchema); - } - } - // Rewrite the type names to remove the app's prefix - SearchResultProto.Builder searchResultsBuilder = searchResults.toBuilder(); - for (int i = 0; i < searchResultsBuilder.getResultsCount(); i++) { - if (searchResults.getResults(i).hasDocument()) { - SearchResultProto.ResultProto.Builder resultBuilder = - searchResultsBuilder.getResults(i).toBuilder(); - - // TODO(b/145631811): Since FakeIcing doesn't currently handle namespaces, we - // perform a post-filter to make sure we don't return documents we shouldn't. This - // should be removed once the real Icing Lib is implemented. - if (!resultBuilder.getDocument().getNamespace().equals(typePrefix)) { - searchResultsBuilder.removeResults(i); - i--; - continue; - } - - // TODO(b/145631811): Since FakeIcing doesn't currently handle type names, we - // perform a post-filter to make sure we don't return documents we shouldn't. This - // should be removed once the real Icing Lib is implemented. - if (qualifiedSearchFilters != null - && !qualifiedSearchFilters.contains( - resultBuilder.getDocument().getSchema())) { - searchResultsBuilder.removeResults(i); - i--; - continue; - } - - DocumentProto.Builder documentBuilder = resultBuilder.getDocument().toBuilder(); - rewriteDocumentTypes(typePrefix, documentBuilder, /*add=*/false); - resultBuilder.setDocument(documentBuilder); - searchResultsBuilder.setResults(i, resultBuilder); - } - } - return searchResultsBuilder.build(); - } - - /** Deletes the given document by URI */ - public boolean delete(int callingUid, @NonNull String uri) { - DocumentProto document = mFakeIcing.get(uri); - if (document == null) { - return false; - } - - // TODO(b/146526096): Since FakeIcing doesn't currently handle namespaces, we perform a - // post-filter to make sure we don't delete documents we shouldn't. This should be - // removed once the real Icing Lib is implemented. - String typePrefix = getTypePrefix(callingUid); - if (!typePrefix.equals(document.getNamespace())) { - throw new SecurityException( - "Failed to delete document " + uri + "; URI collision in FakeIcing"); - } - - return mFakeIcing.delete(uri); - } - - /** Deletes all documents having the given {@code schemaType}. */ - public boolean deleteByType(int callingUid, @NonNull String schemaType) { - String typePrefix = getTypePrefix(callingUid); - String qualifiedType = typePrefix + schemaType; - return mFakeIcing.deleteByType(qualifiedType); - } - - /** - * Deletes all documents owned by the calling app. - * - * @param callingUid The uid of the app calling AppSearch. - */ - public void deleteAll(int callingUid) { - String namespace = getTypePrefix(callingUid); - mFakeIcing.deleteByNamespace(namespace); - } - - /** - * Rewrites all types mentioned anywhere in {@code documentBuilder} to prepend or remove - * {@code typePrefix}. - * - * @param typePrefix The prefix to add or remove - * @param documentBuilder The document to mutate - * @param add Whether to add typePrefix to the types. If {@code false}, typePrefix will be - * removed from the types. - * @throws IllegalArgumentException If {@code add=false} and the document has a type that - * doesn't start with {@code typePrefix}. - */ - @VisibleForTesting - void rewriteDocumentTypes( - @NonNull String typePrefix, - @NonNull DocumentProto.Builder documentBuilder, - boolean add) { - // Rewrite the type name to include/remove the app's prefix - String newSchema; - if (add) { - newSchema = typePrefix + documentBuilder.getSchema(); - } else { - newSchema = removePrefix(typePrefix, documentBuilder.getSchema()); - } - documentBuilder.setSchema(newSchema); - - // Add/remove namespace. If we ever allow users to set their own namespaces, this will have - // to change to prepend the prefix instead of setting the whole namespace. We will also have - // to store the namespaces in a map similar to the type map so we can rewrite queries with - // empty namespaces. - if (add) { - documentBuilder.setNamespace(typePrefix); - } else if (!documentBuilder.getNamespace().equals(typePrefix)) { - throw new IllegalStateException( - "Unexpected namespace \"" + documentBuilder.getNamespace() - + "\" (expected \"" + typePrefix + "\")"); - } else { - documentBuilder.clearNamespace(); - } - - // Recurse into derived documents - for (int propertyIdx = 0; - propertyIdx < documentBuilder.getPropertiesCount(); - propertyIdx++) { - int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount(); - if (documentCount > 0) { - PropertyProto.Builder propertyBuilder = - documentBuilder.getProperties(propertyIdx).toBuilder(); - for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) { - DocumentProto.Builder derivedDocumentBuilder = - propertyBuilder.getDocumentValues(documentIdx).toBuilder(); - rewriteDocumentTypes(typePrefix, derivedDocumentBuilder, add); - propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder); - } - documentBuilder.setProperties(propertyIdx, propertyBuilder); - } - } - } - - /** - * Returns a type prefix in a format like {@code com.example.package@1000/} or - * {@code com.example.sharedname:5678@1000/}. - */ - @NonNull - private String getTypePrefix(int callingUid) { - // For regular apps, this call will return the package name. If callingUid is an - // android:sharedUserId, this value may be another type of name and have a :uid suffix. - String callingUidName = mContext.getPackageManager().getNameForUid(callingUid); - if (callingUidName == null) { - // Not sure how this is possible --- maybe app was uninstalled? - throw new IllegalStateException("Failed to look up package name for uid " + callingUid); - } - return callingUidName + "@" + mUserId + "/"; - } - - @NonNull - private static String removePrefix(@NonNull String prefix, @NonNull String input) { - if (!input.startsWith(prefix)) { - throw new IllegalArgumentException( - "Input \"" + input + "\" does not start with \"" + prefix + "\""); - } - return input.substring(prefix.length()); - } -} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java deleted file mode 100644 index da1573463b73..000000000000 --- a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.appsearch.impl; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.SparseArray; - -import com.google.android.icing.proto.DocumentProto; -import com.google.android.icing.proto.PropertyProto; -import com.google.android.icing.proto.SearchResultProto; -import com.google.android.icing.proto.StatusProto; - -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Fake in-memory implementation of the Icing key-value store and reverse index. - * <p> - * Currently, only queries by single exact term are supported. There is no support for persistence, - * namespaces, i18n tokenization, or schema. - */ -public class FakeIcing { - private final AtomicInteger mNextDocId = new AtomicInteger(); - private final Map<String, Integer> mUriToDocIdMap = new ArrayMap<>(); - /** Array of Documents where index into the array is the docId. */ - private final SparseArray<DocumentProto> mDocStore = new SparseArray<>(); - /** Map of term to posting-list (the set of DocIds containing that term). */ - private final Map<String, Set<Integer>> mIndex = new ArrayMap<>(); - - /** - * Inserts a document into the index. - * - * @param document The document to insert. - */ - public void put(@NonNull DocumentProto document) { - String uri = document.getUri(); - - // Update mDocIdMap - Integer docId = mUriToDocIdMap.get(uri); - if (docId != null) { - // Delete the old doc - mDocStore.remove(docId); - } - - // Allocate a new docId - docId = mNextDocId.getAndIncrement(); - mUriToDocIdMap.put(uri, docId); - - // Update mDocStore - mDocStore.put(docId, document); - - // Update mIndex - indexDocument(docId, document); - } - - /** - * Retrieves a document from the index. - * - * @param uri The URI of the document to retrieve. - * @return The body of the document, or {@code null} if no such document exists. - */ - @Nullable - public DocumentProto get(@NonNull String uri) { - Integer docId = mUriToDocIdMap.get(uri); - if (docId == null) { - return null; - } - return mDocStore.get(docId); - } - - /** - * Returns documents containing all words in the given query string. - * - * @param queryExpression A set of words to search for. They will be implicitly AND-ed together. - * No operators are supported. - * @return A {@link SearchResultProto} containing the matching documents, which may have no - * results if no documents match. - */ - @NonNull - public SearchResultProto query(@NonNull String queryExpression) { - String[] terms = normalizeString(queryExpression).split("\\s+"); - SearchResultProto.Builder results = SearchResultProto.newBuilder() - .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK)); - if (terms.length == 0) { - return results.build(); - } - Set<Integer> docIds = mIndex.get(terms[0]); - if (docIds == null || docIds.isEmpty()) { - return results.build(); - } - for (int i = 1; i < terms.length; i++) { - Set<Integer> termDocIds = mIndex.get(terms[i]); - if (termDocIds == null) { - return results.build(); - } - docIds.retainAll(termDocIds); - if (docIds.isEmpty()) { - return results.build(); - } - } - for (int docId : docIds) { - DocumentProto document = mDocStore.get(docId); - if (document != null) { - results.addResults( - SearchResultProto.ResultProto.newBuilder().setDocument(document)); - } - } - return results.build(); - } - - /** - * Deletes a document by its URI. - * - * @param uri The URI of the document to be deleted. - * @return Whether deletion was successful. - */ - public boolean delete(@NonNull String uri) { - // Update mDocIdMap - Integer docId = mUriToDocIdMap.get(uri); - if (docId != null) { - // Delete the old doc - mDocStore.remove(docId); - mUriToDocIdMap.remove(uri); - return true; - } - return false; - } - - /** Deletes all documents having the given namespace. */ - public void deleteByNamespace(@NonNull String namespace) { - for (int i = 0; i < mDocStore.size(); i++) { - DocumentProto document = mDocStore.valueAt(i); - if (namespace.equals(document.getNamespace())) { - mDocStore.removeAt(i); - mUriToDocIdMap.remove(document.getUri()); - i--; - } - } - } - - /** - * Deletes all documents having the given type. - * - * @return true if any documents were deleted. - */ - public boolean deleteByType(@NonNull String type) { - boolean deletedAny = false; - for (int i = 0; i < mDocStore.size(); i++) { - DocumentProto document = mDocStore.valueAt(i); - if (type.equals(document.getSchema())) { - mDocStore.removeAt(i); - mUriToDocIdMap.remove(document.getUri()); - i--; - deletedAny = true; - } - } - return deletedAny; - } - - private void indexDocument(int docId, DocumentProto document) { - for (PropertyProto property : document.getPropertiesList()) { - for (String stringValue : property.getStringValuesList()) { - String[] words = normalizeString(stringValue).split("\\s+"); - for (String word : words) { - indexTerm(docId, word); - } - } - for (Long longValue : property.getInt64ValuesList()) { - indexTerm(docId, longValue.toString()); - } - for (Double doubleValue : property.getDoubleValuesList()) { - indexTerm(docId, doubleValue.toString()); - } - for (Boolean booleanValue : property.getBooleanValuesList()) { - indexTerm(docId, booleanValue.toString()); - } - // Intentionally skipping bytes values - for (DocumentProto documentValue : property.getDocumentValuesList()) { - indexDocument(docId, documentValue); - } - } - } - - private void indexTerm(int docId, String term) { - Set<Integer> postingList = mIndex.get(term); - if (postingList == null) { - postingList = new ArraySet<>(); - mIndex.put(term, postingList); - } - postingList.add(docId); - } - - /** Strips out punctuation and converts to lowercase. */ - private static String normalizeString(String input) { - return input.replaceAll("\\p{P}", "").toLowerCase(Locale.getDefault()); - } -} diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java index bb9f13f1712c..5cebf8d91cfc 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -128,7 +128,7 @@ class BlobStoreConfig { */ public static final String KEY_USE_REVOCABLE_FD_FOR_READS = "use_revocable_fd_for_reads"; - public static final boolean DEFAULT_USE_REVOCABLE_FD_FOR_READS = true; + public static final boolean DEFAULT_USE_REVOCABLE_FD_FOR_READS = false; public static boolean USE_REVOCABLE_FD_FOR_READS = DEFAULT_USE_REVOCABLE_FD_FOR_READS; diff --git a/apex/media/OWNERS b/apex/media/OWNERS index e83ea3a5087a..ced2fb5e2dcd 100644 --- a/apex/media/OWNERS +++ b/apex/media/OWNERS @@ -1,7 +1,10 @@ andrewlewis@google.com aquilescanta@google.com chz@google.com +hdmoon@google.com hkuang@google.com +jinpark@google.com +klhyun@google.com lnilsson@google.com marcone@google.com sungsoo@google.com diff --git a/apex/media/aidl/Android.bp b/apex/media/aidl/Android.bp index 409a04897f56..c2b00d5849c4 100644 --- a/apex/media/aidl/Android.bp +++ b/apex/media/aidl/Android.bp @@ -15,21 +15,21 @@ // filegroup { - name: "stable-mediasession2-aidl-srcs", + name: "stable-media-aidl-srcs", srcs: ["stable/**/*.aidl"], path: "stable", } filegroup { - name: "private-mediasession2-aidl-srcs", + name: "private-media-aidl-srcs", srcs: ["private/**/I*.aidl"], path: "private", } filegroup { - name: "mediasession2-aidl-srcs", + name: "media-aidl-srcs", srcs: [ - ":private-mediasession2-aidl-srcs", - ":stable-mediasession2-aidl-srcs", + ":private-media-aidl-srcs", + ":stable-media-aidl-srcs", ], } diff --git a/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl b/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl new file mode 100644 index 000000000000..92d673fd25cb --- /dev/null +++ b/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl @@ -0,0 +1,19 @@ +/* + * 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.media; + +parcelable MediaParceledListSlice<T>; diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index ce4b030467a7..813631e28a88 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -54,9 +54,10 @@ java_library { filegroup { name: "updatable-media-srcs", srcs: [ + ":media-aidl-srcs", + ":mediaparceledlistslice-java-srcs", ":mediaparser-srcs", ":mediasession2-java-srcs", - ":mediasession2-aidl-srcs", ], } @@ -77,6 +78,15 @@ filegroup { } filegroup { + name: "mediaparceledlistslice-java-srcs", + srcs: [ + "java/android/media/MediaParceledListSlice.java", + "java/android/media/BaseMediaParceledListSlice.java", + ], + path: "java", +} + +filegroup { name: "mediaparser-srcs", srcs: [ "java/android/media/MediaParser.java" diff --git a/apex/media/framework/api/module-lib-current.txt b/apex/media/framework/api/module-lib-current.txt index d802177e249b..d2826d01664c 100644 --- a/apex/media/framework/api/module-lib-current.txt +++ b/apex/media/framework/api/module-lib-current.txt @@ -1 +1,15 @@ // Signature format: 2.0 +package android.media { + + public final class MediaParceledListSlice<T extends android.os.Parcelable> implements android.os.Parcelable { + ctor public MediaParceledListSlice(@NonNull java.util.List<T>); + method public int describeContents(); + method @NonNull public static <T extends android.os.Parcelable> android.media.MediaParceledListSlice<T> emptyList(); + method public java.util.List<T> getList(); + method public void setInlineCountLimit(int); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.ClassLoaderCreator<android.media.MediaParceledListSlice> CREATOR; + } + +} + diff --git a/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java b/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java new file mode 100644 index 000000000000..fb666098301a --- /dev/null +++ b/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java @@ -0,0 +1,215 @@ +/* + * 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.media; + +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * This is a copied version of BaseParceledListSlice in framework with hidden API usages + * removed. + * + * Transfer a large list of Parcelable objects across an IPC. Splits into + * multiple transactions if needed. + * + * Caveat: for efficiency and security, all elements must be the same concrete type. + * In order to avoid writing the class name of each object, we must ensure that + * each object is the same type, or else unparceling then reparceling the data may yield + * a different result if the class name encoded in the Parcelable is a Base type. + * See b/17671747. + * + * @hide + */ +abstract class BaseMediaParceledListSlice<T> implements Parcelable { + private static String TAG = "BaseMediaParceledListSlice"; + private static boolean DEBUG = false; + + /* + * TODO get this number from somewhere else. For now set it to a quarter of + * the 1MB limit. + */ + // private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes(); + private static final int MAX_IPC_SIZE = 64 * 1024; + + private final List<T> mList; + + private int mInlineCountLimit = Integer.MAX_VALUE; + + public BaseMediaParceledListSlice(List<T> list) { + mList = list; + } + + @SuppressWarnings("unchecked") + BaseMediaParceledListSlice(Parcel p, ClassLoader loader) { + final int N = p.readInt(); + mList = new ArrayList<T>(N); + if (DEBUG) Log.d(TAG, "Retrieving " + N + " items"); + if (N <= 0) { + return; + } + + Parcelable.Creator<?> creator = readParcelableCreator(p, loader); + Class<?> listElementClass = null; + + int i = 0; + while (i < N) { + if (p.readInt() == 0) { + break; + } + + final T parcelable = readCreator(creator, p, loader); + if (listElementClass == null) { + listElementClass = parcelable.getClass(); + } else { + verifySameType(listElementClass, parcelable.getClass()); + } + + mList.add(parcelable); + + if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1)); + i++; + } + if (i >= N) { + return; + } + final IBinder retriever = p.readStrongBinder(); + while (i < N) { + if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever); + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInt(i); + try { + retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0); + } catch (RemoteException e) { + Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e); + return; + } + while (i < N && reply.readInt() != 0) { + final T parcelable = readCreator(creator, reply, loader); + verifySameType(listElementClass, parcelable.getClass()); + + mList.add(parcelable); + + if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1)); + i++; + } + reply.recycle(); + data.recycle(); + } + } + + private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) { + if (creator instanceof Parcelable.ClassLoaderCreator<?>) { + Parcelable.ClassLoaderCreator<?> classLoaderCreator = + (Parcelable.ClassLoaderCreator<?>) creator; + return (T) classLoaderCreator.createFromParcel(p, loader); + } + return (T) creator.createFromParcel(p); + } + + private static void verifySameType(final Class<?> expected, final Class<?> actual) { + if (!actual.equals(expected)) { + throw new IllegalArgumentException("Can't unparcel type " + + (actual == null ? null : actual.getName()) + " in list of type " + + (expected == null ? null : expected.getName())); + } + } + + public List<T> getList() { + return mList; + } + + /** + * Set a limit on the maximum number of entries in the array that will be included + * inline in the initial parcelling of this object. + */ + public void setInlineCountLimit(int maxCount) { + mInlineCountLimit = maxCount; + } + + /** + * Write this to another Parcel. Note that this discards the internal Parcel + * and should not be used anymore. This is so we can pass this to a Binder + * where we won't have a chance to call recycle on this. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + final int N = mList.size(); + final int callFlags = flags; + dest.writeInt(N); + if (DEBUG) Log.d(TAG, "Writing " + N + " items"); + if (N > 0) { + final Class<?> listElementClass = mList.get(0).getClass(); + writeParcelableCreator(mList.get(0), dest); + int i = 0; + while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) { + dest.writeInt(1); + + final T parcelable = mList.get(i); + verifySameType(listElementClass, parcelable.getClass()); + writeElement(parcelable, dest, callFlags); + + if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i)); + i++; + } + if (i < N) { + dest.writeInt(0); + Binder retriever = new Binder() { + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + if (code != FIRST_CALL_TRANSACTION) { + return super.onTransact(code, data, reply, flags); + } + int i = data.readInt(); + if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N); + while (i < N && reply.dataSize() < MAX_IPC_SIZE) { + reply.writeInt(1); + + final T parcelable = mList.get(i); + verifySameType(listElementClass, parcelable.getClass()); + writeElement(parcelable, reply, callFlags); + + if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i)); + i++; + } + if (i < N) { + if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N); + reply.writeInt(0); + } + return true; + } + }; + if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever); + dest.writeStrongBinder(retriever); + } + } + } + + abstract void writeElement(T parcelable, Parcel reply, int callFlags); + + abstract void writeParcelableCreator(T parcelable, Parcel dest); + + abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader); +} diff --git a/apex/media/framework/java/android/media/MediaParceledListSlice.java b/apex/media/framework/java/android/media/MediaParceledListSlice.java new file mode 100644 index 000000000000..e1223f6a6bed --- /dev/null +++ b/apex/media/framework/java/android/media/MediaParceledListSlice.java @@ -0,0 +1,101 @@ +/* + * 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.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Collections; +import java.util.List; + +/** + * This is a copied version of MediaParceledListSlice in framework with hidden API usages removed, + * and also with some lint error fixed. + * + * Transfer a large list of Parcelable objects across an IPC. Splits into + * multiple transactions if needed. + * + * @see BaseMediaParceledListSlice + * + * TODO: Remove this from @SystemApi once all the MediaSession related classes are moved + * to apex (or ParceledListSlice moved to apex). This class is temporaily added to system API + * for moving classes step by step. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class MediaParceledListSlice<T extends Parcelable> + extends BaseMediaParceledListSlice<T> { + public MediaParceledListSlice(@NonNull List<T> list) { + super(list); + } + + private MediaParceledListSlice(Parcel in, ClassLoader loader) { + super(in, loader); + } + + @NonNull + public static <T extends Parcelable> MediaParceledListSlice<T> emptyList() { + return new MediaParceledListSlice<T>(Collections.<T> emptyList()); + } + + @Override + public int describeContents() { + int contents = 0; + final List<T> list = getList(); + for (int i=0; i<list.size(); i++) { + contents |= list.get(i).describeContents(); + } + return contents; + } + + @Override + void writeElement(T parcelable, Parcel dest, int callFlags) { + parcelable.writeToParcel(dest, callFlags); + } + + @Override + void writeParcelableCreator(T parcelable, Parcel dest) { + dest.writeParcelableCreator((Parcelable) parcelable); + } + + @Override + Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader) { + return from.readParcelableCreator(loader); + } + + @NonNull + @SuppressWarnings("unchecked") + public static final Parcelable.ClassLoaderCreator<MediaParceledListSlice> CREATOR = + new Parcelable.ClassLoaderCreator<MediaParceledListSlice>() { + public MediaParceledListSlice createFromParcel(Parcel in) { + return new MediaParceledListSlice(in, null); + } + + @Override + public MediaParceledListSlice createFromParcel(Parcel in, ClassLoader loader) { + return new MediaParceledListSlice(in, loader); + } + + @Override + public MediaParceledListSlice[] newArray(int size) { + return new MediaParceledListSlice[size]; + } + }; +} diff --git a/api/current.txt b/api/current.txt index 4b0901a122e7..09a65c398caa 100644 --- a/api/current.txt +++ b/api/current.txt @@ -21,6 +21,7 @@ package android { field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION"; field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL"; field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS"; + field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS"; field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE"; field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET"; @@ -129,6 +130,7 @@ package android { field public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS"; field public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH"; field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO"; + field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS"; field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"; field public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND"; @@ -6138,6 +6140,7 @@ package android.app { field @NonNull public static final android.os.Parcelable.Creator<android.app.PendingIntent> CREATOR; field public static final int FLAG_CANCEL_CURRENT = 268435456; // 0x10000000 field public static final int FLAG_IMMUTABLE = 67108864; // 0x4000000 + field public static final int FLAG_MUTABLE = 33554432; // 0x2000000 field public static final int FLAG_NO_CREATE = 536870912; // 0x20000000 field public static final int FLAG_ONE_SHOT = 1073741824; // 0x40000000 field public static final int FLAG_UPDATE_CURRENT = 134217728; // 0x8000000 @@ -8783,6 +8786,7 @@ package android.bluetooth { method public void onPhyUpdate(android.bluetooth.BluetoothGatt, int, int, int); method public void onReadRemoteRssi(android.bluetooth.BluetoothGatt, int, int); method public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt, int); + method public void onServiceChanged(@NonNull android.bluetooth.BluetoothGatt); method public void onServicesDiscovered(android.bluetooth.BluetoothGatt, int); } @@ -12233,6 +12237,7 @@ package android.content.pm { field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct"; field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint"; field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt"; + field public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 8; // 0x8 field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2 field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1 field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4 @@ -12342,6 +12347,7 @@ package android.content.pm { field public static final int FLAG_HARD_RESTRICTED = 4; // 0x4 field public static final int FLAG_IMMUTABLY_RESTRICTED = 16; // 0x10 field public static final int FLAG_INSTALLED = 1073741824; // 0x40000000 + field public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 32; // 0x20 field public static final int FLAG_SOFT_RESTRICTED = 8; // 0x8 field public static final int PROTECTION_DANGEROUS = 1; // 0x1 field public static final int PROTECTION_FLAG_APPOP = 64; // 0x40 @@ -16508,6 +16514,23 @@ package android.graphics.pdf { package android.graphics.text { + public class GlyphStyle { + ctor public GlyphStyle(@ColorInt int, @FloatRange(from=0) float, @FloatRange(from=0) float, @FloatRange(from=0) float, int); + ctor public GlyphStyle(@NonNull android.graphics.Paint); + method public void applyToPaint(@NonNull android.graphics.Paint); + method @ColorInt public int getColor(); + method public int getFlags(); + method @FloatRange(from=0) public float getFontSize(); + method @FloatRange(from=0) public float getScaleX(); + method @FloatRange(from=0) public float getSkewX(); + method public void setColor(@ColorInt int); + method public void setFlags(int); + method public void setFontSize(@FloatRange(from=0) float); + method public void setFromPaint(@NonNull android.graphics.Paint); + method public void setScaleX(@FloatRange(from=0) float); + method public void setSkewX(@FloatRange(from=0) float); + } + public class LineBreaker { method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int); field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2 @@ -16568,6 +16591,25 @@ package android.graphics.text { method @NonNull public android.graphics.text.MeasuredText.Builder setComputeLayout(boolean); } + public final class PositionedGlyphs { + method public float getAscent(); + method public float getDescent(); + method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int); + method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int); + method public float getOriginX(); + method public float getOriginY(); + method public float getPositionX(@IntRange(from=0) int); + method public float getPositionY(@IntRange(from=0) int); + method @NonNull public android.graphics.text.GlyphStyle getStyle(); + method public float getTotalAdvance(); + method @IntRange(from=0) public int glyphCount(); + } + + public class TextShaper { + method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull char[], int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint); + method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull CharSequence, int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint); + } + } package android.hardware { @@ -26358,6 +26400,7 @@ package android.media { method public boolean containsKey(String); method public int describeContents(); method public android.graphics.Bitmap getBitmap(String); + method @IntRange(from=0) public int getBitmapDimensionLimit(); method @NonNull public android.media.MediaDescription getDescription(); method public long getLong(String); method public android.media.Rating getRating(String); @@ -26407,6 +26450,7 @@ package android.media { method public android.media.MediaMetadata.Builder putRating(String, android.media.Rating); method public android.media.MediaMetadata.Builder putString(String, String); method public android.media.MediaMetadata.Builder putText(String, CharSequence); + method @NonNull public android.media.MediaMetadata.Builder setBitmapDimensionLimit(int); } @Deprecated public abstract class MediaMetadataEditor { @@ -31567,6 +31611,7 @@ package android.net.wifi { method public boolean isEasyConnectSupported(); method public boolean isEnhancedOpenSupported(); method public boolean isEnhancedPowerReportingSupported(); + method public boolean isMultiStaConcurrencySupported(); method public boolean isP2pSupported(); method public boolean isPreferredNetworkOffloadSupported(); method @Deprecated public boolean isScanAlwaysAvailable(); @@ -46096,6 +46141,7 @@ package android.telecom { field public static final int MISSED = 5; // 0x5 field public static final int OTHER = 9; // 0x9 field public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED"; + field public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL"; field public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED"; field public static final String REASON_WIFI_ON_BUT_WFC_OFF = "REASON_WIFI_ON_BUT_WFC_OFF"; field public static final int REJECTED = 6; // 0x6 @@ -46795,6 +46841,7 @@ package android.telephony { field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; + field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int"; field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool"; field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool"; @@ -46989,6 +47036,10 @@ package android.telephony { field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool"; field public static final int SERVICE_CLASS_NONE = 0; // 0x0 field public static final int SERVICE_CLASS_VOICE = 1; // 0x1 + field public static final int USSD_OVER_CS_ONLY = 2; // 0x2 + field public static final int USSD_OVER_CS_PREFERRED = 0; // 0x0 + field public static final int USSD_OVER_IMS_ONLY = 3; // 0x3 + field public static final int USSD_OVER_IMS_PREFERRED = 1; // 0x1 } public static final class CarrierConfigManager.Apn { @@ -47163,9 +47214,9 @@ package android.telephony { field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellInfoWcdma> CREATOR; } - public abstract class CellLocation { - ctor public CellLocation(); - method public static android.telephony.CellLocation getEmpty(); + @Deprecated public abstract class CellLocation { + ctor @Deprecated public CellLocation(); + method @Deprecated public static android.telephony.CellLocation getEmpty(); method @Deprecated public static void requestLocationUpdate(); } @@ -48683,19 +48734,19 @@ package android.telephony { package android.telephony.cdma { - public class CdmaCellLocation extends android.telephony.CellLocation { - ctor public CdmaCellLocation(); - ctor public CdmaCellLocation(android.os.Bundle); - method public static double convertQuartSecToDecDegrees(int); - method public void fillInNotifierBundle(android.os.Bundle); - method public int getBaseStationId(); - method public int getBaseStationLatitude(); - method public int getBaseStationLongitude(); - method public int getNetworkId(); - method public int getSystemId(); - method public void setCellLocationData(int, int, int); - method public void setCellLocationData(int, int, int, int, int); - method public void setStateInvalid(); + @Deprecated public class CdmaCellLocation extends android.telephony.CellLocation { + ctor @Deprecated public CdmaCellLocation(); + ctor @Deprecated public CdmaCellLocation(android.os.Bundle); + method @Deprecated public static double convertQuartSecToDecDegrees(int); + method @Deprecated public void fillInNotifierBundle(android.os.Bundle); + method @Deprecated public int getBaseStationId(); + method @Deprecated public int getBaseStationLatitude(); + method @Deprecated public int getBaseStationLongitude(); + method @Deprecated public int getNetworkId(); + method @Deprecated public int getSystemId(); + method @Deprecated public void setCellLocationData(int, int, int); + method @Deprecated public void setCellLocationData(int, int, int, int, int); + method @Deprecated public void setStateInvalid(); } } @@ -48895,15 +48946,15 @@ package android.telephony.euicc { package android.telephony.gsm { - public class GsmCellLocation extends android.telephony.CellLocation { - ctor public GsmCellLocation(); - ctor public GsmCellLocation(android.os.Bundle); - method public void fillInNotifierBundle(android.os.Bundle); - method public int getCid(); - method public int getLac(); - method public int getPsc(); - method public void setLacAndCid(int, int); - method public void setStateInvalid(); + @Deprecated public class GsmCellLocation extends android.telephony.CellLocation { + ctor @Deprecated public GsmCellLocation(); + ctor @Deprecated public GsmCellLocation(android.os.Bundle); + method @Deprecated public void fillInNotifierBundle(android.os.Bundle); + method @Deprecated public int getCid(); + method @Deprecated public int getLac(); + method @Deprecated public int getPsc(); + method @Deprecated public void setLacAndCid(int, int); + method @Deprecated public void setStateInvalid(); } @Deprecated public final class SmsManager { @@ -49990,6 +50041,10 @@ package android.text { method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean); } + public class StyledTextShaper { + method @NonNull public static java.util.List<android.graphics.text.PositionedGlyphs> shapeText(@NonNull CharSequence, int, int, @NonNull android.text.TextDirectionHeuristic, @NonNull android.text.TextPaint); + } + public interface TextDirectionHeuristic { method public boolean isRtl(char[], int, int); method public boolean isRtl(CharSequence, int, int); @@ -53642,6 +53697,33 @@ package android.view { field public int toolType; } + public interface OnReceiveContentCallback<T extends android.view.View> { + method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull T); + method public boolean onReceiveContent(@NonNull T, @NonNull android.view.OnReceiveContentCallback.Payload); + } + + public static final class OnReceiveContentCallback.Payload { + method @NonNull public android.content.ClipData getClip(); + method @Nullable public android.os.Bundle getExtras(); + method public int getFlags(); + method @Nullable public android.net.Uri getLinkUri(); + method public int getSource(); + field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1 + field public static final int SOURCE_AUTOFILL = 3; // 0x3 + field public static final int SOURCE_CLIPBOARD = 0; // 0x0 + field public static final int SOURCE_DRAG_AND_DROP = 2; // 0x2 + field public static final int SOURCE_INPUT_METHOD = 1; // 0x1 + field public static final int SOURCE_PROCESS_TEXT = 4; // 0x4 + } + + public static final class OnReceiveContentCallback.Payload.Builder { + ctor public OnReceiveContentCallback.Payload.Builder(@NonNull android.content.ClipData, int); + method @NonNull public android.view.OnReceiveContentCallback.Payload build(); + method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setExtras(@Nullable android.os.Bundle); + method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setFlags(int); + method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setLinkUri(@Nullable android.net.Uri); + } + public abstract class OrientationEventListener { ctor public OrientationEventListener(android.content.Context); ctor public OrientationEventListener(android.content.Context, int); @@ -54193,6 +54275,7 @@ package android.view { method @IdRes public int getNextFocusRightId(); method @IdRes public int getNextFocusUpId(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); + method @Nullable public android.view.OnReceiveContentCallback<? extends android.view.View> getOnReceiveContentCallback(); method @ColorInt public int getOutlineAmbientShadowColor(); method public android.view.ViewOutlineProvider getOutlineProvider(); method @ColorInt public int getOutlineSpotShadowColor(); @@ -54544,6 +54627,7 @@ package android.view { method public void setOnHoverListener(android.view.View.OnHoverListener); method public void setOnKeyListener(android.view.View.OnKeyListener); method public void setOnLongClickListener(@Nullable android.view.View.OnLongClickListener); + method public void setOnReceiveContentCallback(@Nullable android.view.OnReceiveContentCallback<? extends android.view.View>); method public void setOnScrollChangeListener(android.view.View.OnScrollChangeListener); method @Deprecated public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener); method public void setOnTouchListener(android.view.View.OnTouchListener); @@ -60806,17 +60890,6 @@ package android.widget { method public android.view.View newGroupView(android.content.Context, android.database.Cursor, boolean, android.view.ViewGroup); } - public interface RichContentReceiver<T extends android.view.View> { - method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(); - method public boolean onReceive(@NonNull T, @NonNull android.content.ClipData, int, int); - field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1 - field public static final int SOURCE_AUTOFILL = 3; // 0x3 - field public static final int SOURCE_CLIPBOARD = 0; // 0x0 - field public static final int SOURCE_DRAG_AND_DROP = 2; // 0x2 - field public static final int SOURCE_INPUT_METHOD = 1; // 0x1 - field public static final int SOURCE_PROCESS_TEXT = 4; // 0x4 - } - public class ScrollView extends android.widget.FrameLayout { ctor public ScrollView(android.content.Context); ctor public ScrollView(android.content.Context, android.util.AttributeSet); @@ -61371,10 +61444,10 @@ package android.widget { method public int getMinWidth(); method public final android.text.method.MovementMethod getMovementMethod(); method public int getOffsetForPosition(float, float); + method @Nullable public android.view.OnReceiveContentCallback<android.widget.TextView> getOnReceiveContentCallback(); method public android.text.TextPaint getPaint(); method public int getPaintFlags(); method public String getPrivateImeOptions(); - method @NonNull public android.widget.RichContentReceiver<android.widget.TextView> getRichContentReceiver(); method public int getSelectionEnd(); method public int getSelectionStart(); method @ColorInt public int getShadowColor(); @@ -61502,7 +61575,6 @@ package android.widget { method public void setPaintFlags(int); method public void setPrivateImeOptions(String); method public void setRawInputType(int); - method public void setRichContentReceiver(@NonNull android.widget.RichContentReceiver<android.widget.TextView>); method public void setScroller(android.widget.Scroller); method public void setSelectAllOnFocus(boolean); method public void setShadowLayer(float, float, float, int); @@ -61543,7 +61615,6 @@ package android.widget { method public void setWidth(int); field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0 field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1 - field @NonNull public static final android.widget.RichContentReceiver<android.widget.TextView> DEFAULT_RICH_CONTENT_RECEIVER; } public enum TextView.BufferType { @@ -61560,6 +61631,12 @@ package android.widget { field @NonNull public static final android.os.Parcelable.Creator<android.widget.TextView.SavedState> CREATOR; } + public class TextViewOnReceiveContentCallback implements android.view.OnReceiveContentCallback<android.widget.TextView> { + ctor public TextViewOnReceiveContentCallback(); + method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull android.widget.TextView); + method public boolean onReceiveContent(@NonNull android.widget.TextView, @NonNull android.view.OnReceiveContentCallback.Payload); + } + public interface ThemedSpinnerAdapter extends android.widget.SpinnerAdapter { method @Nullable public android.content.res.Resources.Theme getDropDownViewTheme(); method public void setDropDownViewTheme(@Nullable android.content.res.Resources.Theme); diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index be8ea9ca6a4d..4a7c1210267e 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -16,6 +16,14 @@ package android.app { } +package android.app.role { + + public final class RoleManager { + method @Nullable public String getDefaultSmsPackage(int); + } + +} + package android.content.rollback { public class RollbackManagerFrameworkInitializer { @@ -45,8 +53,14 @@ package android.media { field public static final int FLAG_FROM_KEY = 4096; // 0x1000 } - public static final class MediaMetadata.Builder { - ctor public MediaMetadata.Builder(@NonNull android.media.MediaMetadata, @IntRange(from=1) int); + public final class MediaParceledListSlice<T extends android.os.Parcelable> implements android.os.Parcelable { + ctor public MediaParceledListSlice(@NonNull java.util.List<T>); + method public int describeContents(); + method @NonNull public static <T extends android.os.Parcelable> android.media.MediaParceledListSlice<T> emptyList(); + method public java.util.List<T> getList(); + method public void setInlineCountLimit(int); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.ClassLoaderCreator<android.media.MediaParceledListSlice> CREATOR; } } diff --git a/api/system-current.txt b/api/system-current.txt index 91be5bdab8b4..aa9536e00bd4 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -2157,6 +2157,7 @@ package android.content.pm { field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000 field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4 field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800 + field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000 field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000 field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000 field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40 @@ -7429,7 +7430,7 @@ package android.net.wifi { field public static final int EASY_CONNECT_NETWORK_ROLE_STA = 0; // 0x0 field public static final String EXTRA_CHANGE_REASON = "changeReason"; field @Deprecated public static final String EXTRA_LINK_PROPERTIES = "android.net.wifi.extra.LINK_PROPERTIES"; - field public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges"; + field @Deprecated public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges"; field public static final String EXTRA_OSU_NETWORK = "android.net.wifi.extra.OSU_NETWORK"; field public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state"; field public static final String EXTRA_URL = "android.net.wifi.extra.URL"; @@ -7437,7 +7438,7 @@ package android.net.wifi { field public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME"; field public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE"; field public static final String EXTRA_WIFI_AP_STATE = "wifi_state"; - field public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration"; + field @Deprecated public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration"; field public static final String EXTRA_WIFI_CREDENTIAL_EVENT_TYPE = "et"; field public static final String EXTRA_WIFI_CREDENTIAL_SSID = "ssid"; field public static final int IFACE_IP_MODE_CONFIGURATION_ERROR = 0; // 0x0 @@ -7565,10 +7566,12 @@ package android.net.wifi { public final class WifiNetworkSuggestion implements android.os.Parcelable { method @NonNull public android.net.wifi.WifiConfiguration getWifiConfiguration(); + method public boolean isOemPaid(); } public static final class WifiNetworkSuggestion.Builder { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int); + method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setOemPaid(boolean); } public class WifiScanner { @@ -10441,10 +10444,6 @@ package android.telecom { method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference); } - public final class DisconnectCause implements android.os.Parcelable { - field public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL"; - } - public abstract class InCallService extends android.app.Service { method @Deprecated public android.telecom.Phone getPhone(); method @Deprecated public void onPhoneCreated(android.telecom.Phone); @@ -10722,16 +10721,12 @@ package android.telephony { method public int getTimeoutSeconds(); method public boolean isEnabled(); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR; - field public static final int ERROR_FDN_CHECK_FAILURE = 2; // 0x2 - field public static final int ERROR_NOT_SUPPORTED = 3; // 0x3 - field public static final int ERROR_UNKNOWN = 1; // 0x1 field public static final int REASON_ALL = 4; // 0x4 field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5 field public static final int REASON_BUSY = 1; // 0x1 field public static final int REASON_NOT_REACHABLE = 3; // 0x3 field public static final int REASON_NO_REPLY = 2; // 0x2 field public static final int REASON_UNCONDITIONAL = 0; // 0x0 - field public static final int SUCCESS = 0; // 0x0 } public final class CallQuality implements android.os.Parcelable { @@ -11474,7 +11469,7 @@ package android.telephony { method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingStatus(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int); @@ -11573,6 +11568,10 @@ package android.telephony { public static interface TelephonyManager.CallForwardingInfoCallback { method public void onCallForwardingInfoAvailable(@NonNull android.telephony.CallForwardingInfo); method public void onError(int); + field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 2; // 0x2 + field public static final int RESULT_ERROR_NOT_SUPPORTED = 3; // 0x3 + field public static final int RESULT_ERROR_UNKNOWN = 1; // 0x1 + field public static final int RESULT_SUCCESS = 0; // 0x0 } public final class UiccAccessRule implements android.os.Parcelable { @@ -12964,11 +12963,12 @@ package android.webkit { } public interface PacProcessor { + method @NonNull public static android.webkit.PacProcessor createInstance(); method @Nullable public String findProxyForUrl(@NonNull String); method @NonNull public static android.webkit.PacProcessor getInstance(); - method @NonNull public static android.webkit.PacProcessor getInstanceForNetwork(@Nullable android.net.Network); method @Nullable public default android.net.Network getNetwork(); method public default void releasePacProcessor(); + method public default void setNetwork(@Nullable android.net.Network); method public boolean setProxyScript(@NonNull String); } @@ -13104,11 +13104,11 @@ package android.webkit { } public interface WebViewFactoryProvider { + method @NonNull public default android.webkit.PacProcessor createPacProcessor(); method public android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess); method public android.webkit.CookieManager getCookieManager(); method public android.webkit.GeolocationPermissions getGeolocationPermissions(); method @NonNull public default android.webkit.PacProcessor getPacProcessor(); - method @NonNull public default android.webkit.PacProcessor getPacProcessorForNetwork(@Nullable android.net.Network); method public android.webkit.ServiceWorkerController getServiceWorkerController(); method public android.webkit.WebViewFactoryProvider.Statics getStatics(); method @Deprecated public android.webkit.TokenBindingService getTokenBindingService(); diff --git a/api/test-current.txt b/api/test-current.txt index 9383152722be..054005444dee 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -83,6 +83,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getPackageImportance(String); method public long getTotalRam(); method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidImportance(int); + method @RequiresPermission("android.permission.INJECT_EVENTS") public void holdLock(int); method public static boolean isHighEndGfx(); method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void killProcessesWhenImperceptible(@NonNull int[], @NonNull String); method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener); @@ -766,6 +767,7 @@ package android.app.role { method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean addRoleHolderFromController(@NonNull String, @NonNull String); method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); + method @Nullable public String getDefaultSmsPackage(int); method @NonNull @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public java.util.List<java.lang.String> getHeldRolesFromController(@NonNull String); method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHolders(@NonNull String); method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle); @@ -1022,6 +1024,7 @@ package android.content.pm { method @Nullable public String getSystemTextClassifierPackageName(); method @Nullable public String getWellbeingPackageName(); method @RequiresPermission("android.permission.GRANT_RUNTIME_PERMISSIONS") public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); + method @RequiresPermission("android.permission.INJECT_EVENTS") public void holdLock(int); method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener); method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public abstract void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull String); @@ -1034,6 +1037,7 @@ package android.content.pm { field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000 field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4 field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800 + field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000 field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000 field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000 field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40 @@ -3405,6 +3409,8 @@ package android.provider { field public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component"; field public static final String NOTIFICATION_BADGING = "notification_badging"; field public static final String POWER_MENU_LOCKED_SHOW_CONTENT = "power_menu_locked_show_content"; + field public static final String SELECTED_SPELL_CHECKER = "selected_spell_checker"; + field public static final String SELECTED_SPELL_CHECKER_SUBTYPE = "selected_spell_checker_subtype"; field public static final String SHOW_FIRST_CRASH_DIALOG_DEV_OPTION = "show_first_crash_dialog_dev_option"; field public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard"; field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds"; @@ -5288,6 +5294,7 @@ package android.view { } public interface WindowManager extends android.view.ViewManager { + method @RequiresPermission("android.permission.INJECT_EVENTS") public default void holdLock(int); method public default void setShouldShowIme(int, boolean); method public default void setShouldShowSystemDecors(int, boolean); method public default void setShouldShowWithInsecureKeyguard(int, boolean); @@ -5537,6 +5544,18 @@ package android.view.inspector { } +package android.view.textservice { + + public final class SpellCheckerSubtype implements android.os.Parcelable { + field public static final int SUBTYPE_ID_NONE = 0; // 0x0 + } + + public final class TextServicesManager { + method public boolean isSpellCheckerEnabled(); + } + +} + package android.widget { public abstract class AbsListView extends android.widget.AdapterView<android.widget.ListAdapter> implements android.widget.Filter.FilterListener android.text.TextWatcher android.view.ViewTreeObserver.OnGlobalLayoutListener android.view.ViewTreeObserver.OnTouchModeChangeListener { @@ -5580,6 +5599,11 @@ package android.widget { method public void disableClockTick(); } + @android.widget.RemoteViews.RemoteView public class TextView extends android.view.View implements android.view.ViewTreeObserver.OnPreDrawListener { + method public void onActivityResult(int, int, @Nullable android.content.Intent); + field public static final int PROCESS_TEXT_REQUEST_CODE = 100; // 0x64 + } + public class TimePicker extends android.widget.FrameLayout { method public android.view.View getAmView(); method public android.view.View getHourView(); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 4ccc7e64308a..42b560d87dd0 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -38,7 +38,6 @@ import "frameworks/base/core/proto/android/net/networkcapabilities.proto"; import "frameworks/base/core/proto/android/os/enums.proto"; import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto"; import "frameworks/base/core/proto/android/server/enums.proto"; -import "frameworks/base/core/proto/android/stats/hdmi/enums.proto"; import "frameworks/base/core/proto/android/server/job/enums.proto"; import "frameworks/base/core/proto/android/server/location/enums.proto"; import "frameworks/base/core/proto/android/service/procstats_enum.proto"; @@ -51,6 +50,7 @@ import "frameworks/base/core/proto/android/stats/devicepolicy/device_policy_enum import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto"; import "frameworks/base/core/proto/android/stats/accessibility/accessibility_enums.proto"; import "frameworks/base/core/proto/android/stats/enums.proto"; +import "frameworks/base/core/proto/android/stats/hdmi/enums.proto"; import "frameworks/base/core/proto/android/stats/intelligence/enums.proto"; import "frameworks/base/core/proto/android/stats/launcher/launcher.proto"; import "frameworks/base/core/proto/android/stats/location/location_enums.proto"; @@ -499,7 +499,7 @@ message Atom { } // Pulled events will start at field 10000. - // Next: 10084 + // Next: 10087 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000 [(module) = "framework"]; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001 [(module) = "framework"]; @@ -598,6 +598,7 @@ message Atom { DNDModeProto dnd_mode_rule = 10084 [(module) = "framework"]; GeneralExternalStorageAccessStats general_external_storage_access_stats = 10085 [(module) = "mediaprovider"]; + IncomingSms incoming_sms = 10086 [(module) = "telephony"]; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -3191,6 +3192,8 @@ message BackGesture { optional int32 end_y = 7; // Y coordinate for ACTION_MOVE event. optional int32 left_boundary = 8; // left edge width + left inset optional int32 right_boundary = 9; // screen width - (right edge width + right inset) + optional float ml_model_score = 10; // The score between 0 and 1 which is the prediction output + // for the Back Gesture model. enum WindowHorizontalLocation { DEFAULT_LOCATION = 0; @@ -10461,6 +10464,59 @@ message SupportedRadioAccessFamily { } /** + * Pulls information for a single incoming SMS. + * + * Each pull creates multiple atoms, one for each SMS. The sequence is randomized when pulled. + * + * Pulled from: + * frameworks/opt/telephony/src/java/com/android/internal/telephony/metrics/MetricsCollector.java + */ +message IncomingSms { + // Format of the SMS (3GPP or 3GPP2). + optional android.telephony.SmsFormatEnum sms_format = 1; + + // Technology of the SMS (CS or IMS). + optional android.telephony.SmsTechEnum sms_tech = 2; + + // Radio access technology (RAT) used for the SMS. It can be IWLAN in case of IMS. + optional android.telephony.NetworkTypeEnum rat = 3; + + // Type the SMS. + optional android.telephony.SmsTypeEnum sms_type = 4; + + // Number of total parts. + optional int32 total_parts = 5; + + // Number of received parts (if smaller than total parts, the SMS was dropped). + optional int32 received_parts = 6; + + // Indicates if the incoming SMS was blocked. + optional bool blocked = 7; + + // Indicate a specific error handling the SMS + optional android.telephony.SmsIncomingErrorEnum error = 8; + + // Whether the SMS was received while roaming. + optional bool is_roaming = 9; + + // Index of the SIM is used, 0 for single-SIM devices. + optional int32 sim_slot_index = 10; + + // Whether the device was in multi-SIM mode (with multiple active SIM profiles). + optional bool is_multi_sim = 11; + + // Whether the message was received with an eSIM profile. + optional bool is_esim = 12; + + // Carrier ID of the SIM card used for the SMS. + // See https://source.android.com/devices/tech/config/carrierid. + optional int32 carrier_id = 13; + + // Random message ID. + optional int64 message_id = 14; +} + +/** * Logs gnss stats from location service provider * * Pulled from: diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp index cd9c4e5b947b..03b178a989eb 100644 --- a/cmds/statsd/src/main.cpp +++ b/cmds/statsd/src/main.cpp @@ -76,7 +76,7 @@ int main(int /*argc*/, char** /*argv*/) { ABinderProcess_startThreadPool(); std::shared_ptr<LogEventQueue> eventQueue = - std::make_shared<LogEventQueue>(2000 /*buffer limit. Buffer is NOT pre-allocated*/); + std::make_shared<LogEventQueue>(4000 /*buffer limit. Buffer is NOT pre-allocated*/); // Create the service gStatsService = SharedRefBase::make<StatsService>(looper, eventQueue); diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp index 2ae57638791e..12d3b9487635 100644 --- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp +++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp @@ -501,6 +501,21 @@ bool determineAllMetricUpdateStatuses(const StatsdConfig& config, const set<int64_t>& replacedStates, vector<UpdateStatus>& metricsToUpdate) { int metricIndex = 0; + for (int i = 0; i < config.count_metric_size(); i++, metricIndex++) { + const CountMetric& metric = config.count_metric(i); + set<int64_t> conditionDependencies; + if (metric.has_condition()) { + conditionDependencies.insert(metric.condition()); + } + if (!determineMetricUpdateStatus( + config, metric, metric.id(), METRIC_TYPE_COUNT, {metric.what()}, + conditionDependencies, metric.slice_by_state(), metric.links(), + oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + replacedMatchers, replacedConditions, replacedStates, + metricsToUpdate[metricIndex])) { + return false; + } + } for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) { const EventMetric& metric = config.event_metric(i); set<int64_t> conditionDependencies; diff --git a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp index 65e5875e39bf..3b346c11e831 100644 --- a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp +++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp @@ -1094,6 +1094,162 @@ TEST_F(ConfigUpdateTest, TestEventMetricActivationDepsChange) { EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); } +TEST_F(ConfigUpdateTest, TestCountMetricPreserve) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + State sliceState = CreateScreenState(); + *config.add_state() = sliceState; + + CountMetric* metric = config.add_count_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + metric->add_slice_by_state(sliceState.id()); + metric->set_bucket(ONE_HOUR); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map<int64_t, int> metricToActivationMap; + vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE); +} + +TEST_F(ConfigUpdateTest, TestCountMetricDefinitionChange) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + CountMetric* metric = config.add_count_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + metric->set_bucket(ONE_HOUR); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + // Change bucket size, which should change the proto, causing replacement. + metric->set_bucket(TEN_MINUTES); + + unordered_map<int64_t, int> metricToActivationMap; + vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestCountMetricWhatChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + CountMetric* metric = config.add_count_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + metric->set_bucket(ONE_HOUR); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map<int64_t, int> metricToActivationMap; + vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestCountMetricConditionChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + CountMetric* metric = config.add_count_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + metric->set_bucket(ONE_HOUR); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map<int64_t, int> metricToActivationMap; + vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestCountMetricStateChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + State sliceState = CreateScreenState(); + *config.add_state() = sliceState; + + CountMetric* metric = config.add_count_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->add_slice_by_state(sliceState.id()); + metric->set_bucket(ONE_HOUR); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map<int64_t, int> metricToActivationMap; + vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{sliceState.id()}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + TEST_F(ConfigUpdateTest, TestUpdateEventMetrics) { StatsdConfig config; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 70ca49d67930..e75d2f60bb87 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4796,4 +4796,19 @@ public class ActivityManager { throw e.rethrowFromSystemServer(); } } + + /** + * Holds the AM lock for the specified amount of milliseconds. + * This is intended for use by the tests that need to imitate lock contention. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) + public void holdLock(int durationMs) { + try { + getService().holdLock(durationMs); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 167b5a8029c0..ef4f099f441d 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -81,8 +81,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.math.RoundingMode; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; @@ -5021,8 +5019,7 @@ public class AppOpsManager { * @hide */ public static double round(double value) { - final BigDecimal decimalScale = new BigDecimal(value); - return decimalScale.setScale(0, RoundingMode.HALF_UP).doubleValue(); + return Math.floor(value + 0.5); } @Override diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 95bbebecd66e..1a4db4e541cc 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -696,4 +696,10 @@ interface IActivityManager { * @param enable set it to true to enable the app freezer, false to disable it. */ boolean enableAppFreezer(in boolean enable); + + /** + * Holds the AM lock for the specified amount of milliseconds. + * This is intended for use by the tests that need to imitate lock contention. + */ + void holdLock(in int durationMs); } diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java index 59dc9991c832..55fff8b2f074 100644 --- a/core/java/android/app/NotificationHistory.java +++ b/core/java/android/app/NotificationHistory.java @@ -22,12 +22,10 @@ import android.graphics.drawable.Icon; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import android.util.Slog; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -387,12 +385,13 @@ public final class NotificationHistory implements Parcelable { /** * Removes all notifications from a conversation and regenerates the string pool */ - public boolean removeConversationFromWrite(String packageName, String conversationId) { + public boolean removeConversationsFromWrite(String packageName, Set<String> conversationIds) { boolean removed = false; for (int i = mNotificationsToWrite.size() - 1; i >= 0; i--) { HistoricalNotification hn = mNotificationsToWrite.get(i); if (packageName.equals(hn.getPackage()) - && conversationId.equals(hn.getConversationId())) { + && hn.getConversationId() != null + && conversationIds.contains(hn.getConversationId())) { removed = true; mNotificationsToWrite.remove(i); } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index cd352e141994..e8937a8da911 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -19,12 +19,16 @@ package android.app; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.IIntentReceiver; import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -35,6 +39,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.AndroidException; import android.util.ArraySet; +import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.internal.os.IResultReceiver; @@ -102,11 +107,20 @@ import java.lang.annotation.RetentionPolicy; * FLAG_ONE_SHOT, <b>both</b> FLAG_ONE_SHOT and FLAG_NO_CREATE need to be supplied. */ public final class PendingIntent implements Parcelable { + private static final String TAG = "PendingIntent"; private final IIntentSender mTarget; private IResultReceiver mCancelReceiver; private IBinder mWhitelistToken; private ArraySet<CancelListener> mCancelListeners; + /** + * It is now required to specify either {@link #FLAG_IMMUTABLE} + * or {@link #FLAG_MUTABLE} when creating a PendingIntent. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.R) + static final long PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED = 160794467L; + /** @hide */ @IntDef(flag = true, value = { @@ -115,6 +129,7 @@ public final class PendingIntent implements Parcelable { FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT, FLAG_IMMUTABLE, + FLAG_MUTABLE, Intent.FILL_IN_ACTION, Intent.FILL_IN_DATA, @@ -175,6 +190,21 @@ public final class PendingIntent implements Parcelable { public static final int FLAG_IMMUTABLE = 1<<26; /** + * Flag indicating that the created PendingIntent should be mutable. + * This flag cannot be combined with {@link #FLAG_IMMUTABLE}. <p>Up until + * {@link android.os.Build.VERSION_CODES#R}, PendingIntents are assumed to + * be mutable by default, unless {@link #FLAG_IMMUTABLE} is set. Starting + * with {@link android.os.Build.VERSION_CODES#S}, it will be required to + * explicitly specify the mutability of PendingIntents on creation with + * either (@link #FLAG_IMMUTABLE} or {@link #FLAG_MUTABLE}. It is strongly + * recommended to use {@link #FLAG_IMMUTABLE} when creating a + * PendingIntent. {@link #FLAG_MUTABLE} should only be used when some + * functionality relies on modifying the underlying intent, e.g. any + * PendingIntent that needs to be used with inline reply or bubbles. + */ + public static final int FLAG_MUTABLE = 1<<25; + + /** * Exception thrown when trying to send through a PendingIntent that * has been canceled or is otherwise no longer able to execute the request. */ @@ -286,6 +316,27 @@ public final class PendingIntent implements Parcelable { sOnMarshaledListener.set(listener); } + private static void checkFlags(int flags, String packageName) { + final boolean flagImmutableSet = (flags & PendingIntent.FLAG_IMMUTABLE) != 0; + final boolean flagMutableSet = (flags & PendingIntent.FLAG_MUTABLE) != 0; + String msg = packageName + ": Targeting S+ (version " + Build.VERSION_CODES.S + + " and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE" + + " be specified when creating a PendingIntent.\nStrongly consider" + + " using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality" + + " depends on the PendingIntent being mutable, e.g. if it needs to" + + " be used with inline replies or bubbles."; + + if (flagImmutableSet && flagMutableSet) { + throw new IllegalArgumentException( + "Cannot set both FLAG_IMMUTABLE and FLAG_MUTABLE for PendingIntent"); + } + + if (Compatibility.isChangeEnabled(PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED) + && !flagImmutableSet && !flagMutableSet) { + Log.e(TAG, msg); + } + } + /** * Retrieve a PendingIntent that will start a new activity, like calling * {@link Context#startActivity(Intent) Context.startActivity(Intent)}. @@ -350,6 +401,7 @@ public final class PendingIntent implements Parcelable { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; + checkFlags(flags, packageName); try { intent.migrateExtraStreamToClipData(context); intent.prepareToLeaveProcess(context); @@ -376,6 +428,7 @@ public final class PendingIntent implements Parcelable { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; + checkFlags(flags, packageName); try { intent.migrateExtraStreamToClipData(context); intent.prepareToLeaveProcess(context); @@ -495,6 +548,7 @@ public final class PendingIntent implements Parcelable { intents[i].prepareToLeaveProcess(context); resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver()); } + checkFlags(flags, packageName); try { IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature( @@ -521,6 +575,7 @@ public final class PendingIntent implements Parcelable { intents[i].prepareToLeaveProcess(context); resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver()); } + checkFlags(flags, packageName); try { IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature( @@ -572,6 +627,7 @@ public final class PendingIntent implements Parcelable { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; + checkFlags(flags, packageName); try { intent.prepareToLeaveProcess(context); IIntentSender target = @@ -651,6 +707,7 @@ public final class PendingIntent implements Parcelable { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; + checkFlags(flags, packageName); try { intent.prepareToLeaveProcess(context); IIntentSender target = diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index d50cdeed6d73..9100d577fd68 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -33,6 +33,7 @@ import android.app.prediction.AppPredictionManager; import android.app.role.RoleControllerManager; import android.app.role.RoleManager; import android.app.slice.SliceManager; +import android.app.time.TimeManager; import android.app.timedetector.TimeDetector; import android.app.timedetector.TimeDetectorImpl; import android.app.timezone.RulesManager; @@ -1218,6 +1219,14 @@ public final class SystemServiceRegistry { return new TimeZoneDetectorImpl(); }}); + registerService(Context.TIME_MANAGER, TimeManager.class, + new CachedServiceFetcher<TimeManager>() { + @Override + public TimeManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + return new TimeManager(); + }}); + registerService(Context.PERMISSION_SERVICE, PermissionManager.class, new CachedServiceFetcher<PermissionManager>() { @Override diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index a2f44149e853..3cc7f1e5df42 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -55,14 +55,6 @@ public final class DeviceAdminInfo implements Parcelable { static final String TAG = "DeviceAdminInfo"; /** - * A type of policy that this device admin can use: profile owner on an organization-owned - * device. - * - * @hide - */ - public static final int USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER = -3; - - /** * A type of policy that this device admin can use: device owner meta-policy * for an admin that is designated as owner of the device. * diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl index 61dac0d64422..c547ef1e7e9b 100644 --- a/core/java/android/app/people/IPeopleManager.aidl +++ b/core/java/android/app/people/IPeopleManager.aidl @@ -39,4 +39,10 @@ interface IPeopleManager { /** Removes all the recent conversations and uncaches their cached shortcuts. */ void removeAllRecentConversations(); + + /** + * Returns the last interaction with the specified conversation. If the + * conversation can't be found or no interactions have been recorded, returns 0L. + */ + long getLastInteraction(in String packageName, int userId, in String shortcutId); } diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java index 253c73796caf..26edba36fdba 100644 --- a/core/java/android/app/role/RoleManager.java +++ b/core/java/android/app/role/RoleManager.java @@ -634,6 +634,8 @@ public final class RoleManager { * @hide */ @Nullable + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @TestApi public String getDefaultSmsPackage(@UserIdInt int userId) { try { return mService.getDefaultSmsPackage(userId); diff --git a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl b/core/java/android/app/time/ITimeZoneDetectorListener.aidl index 6d0fe72b9de1..723ad5969afc 100644 --- a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl +++ b/core/java/android/app/time/ITimeZoneDetectorListener.aidl @@ -14,11 +14,9 @@ * limitations under the License. */ -package android.app.timezonedetector; - -import android.app.timezonedetector.TimeZoneConfiguration; +package android.app.time; /** {@hide} */ -oneway interface ITimeZoneConfigurationListener { +oneway interface ITimeZoneDetectorListener { void onChange(); }
\ No newline at end of file diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING new file mode 100644 index 000000000000..951905bcbac5 --- /dev/null +++ b/core/java/android/app/time/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.app.time." + } + ] + } + ] +} diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java new file mode 100644 index 000000000000..9864afba534a --- /dev/null +++ b/core/java/android/app/time/TimeManager.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.time; + +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemService; +import android.app.timezonedetector.ITimeZoneDetectorService; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.concurrent.Executor; + +/** + * The interface through which system components can interact with time and time zone services. + * + * @hide + */ +// @SystemApi +@SystemService(Context.TIME_MANAGER) +public final class TimeManager { + private static final String TAG = "time.TimeManager"; + private static final boolean DEBUG = false; + + private final Object mLock = new Object(); + private final ITimeZoneDetectorService mITimeZoneDetectorService; + + @GuardedBy("mLock") + private ITimeZoneDetectorListener mTimeZoneDetectorReceiver; + + /** + * The registered listeners. The key is the actual listener that was registered, the value is a + * wrapper that ensures the listener is executed on the correct Executor. + */ + @GuardedBy("mLock") + private ArrayMap<TimeZoneDetectorListener, TimeZoneDetectorListener> mTimeZoneDetectorListeners; + + /** @hide */ + public TimeManager() throws ServiceNotFoundException { + // TimeManager is an API over one or possibly more services. At least until there's an + // internal refactoring. + mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE)); + } + + /** + * Returns the calling user's time zone capabilities and configuration. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) + @NonNull + public TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig() { + if (DEBUG) { + Log.d(TAG, "getTimeZoneCapabilities called"); + } + try { + return mITimeZoneDetectorService.getCapabilitiesAndConfig(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Modifies the time zone detection configuration. + * + * <p>Configuration settings vary in scope: some may be global (affect all users), others may be + * specific to the current user. + * + * <p>The ability to modify configuration settings can be subject to restrictions. For + * example, they may be determined by device hardware, general policy (i.e. only the primary + * user can set them), or by a managed device policy. Use {@link + * #getTimeZoneCapabilitiesAndConfig()} to obtain information at runtime about the user's + * capabilities. + * + * <p>Attempts to modify configuration settings with capabilities that are {@link + * TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link + * TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false} + * will be returned. Modifying configuration settings with capabilities that are {@link + * TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link + * TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link + * TimeZoneCapabilities} for further details. + * + * <p>If the supplied configuration only has some values set, then only the specified settings + * will be updated (where the user's capabilities allow) and other settings will be left + * unchanged. + * + * @return {@code true} if all the configuration settings specified have been set to the + * new values, {@code false} if none have + */ + @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) + public boolean updateTimeZoneConfiguration(@NonNull TimeZoneConfiguration configuration) { + if (DEBUG) { + Log.d(TAG, "updateConfiguration called: " + configuration); + } + try { + return mITimeZoneDetectorService.updateConfiguration(configuration); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * An interface that can be used to listen for changes to the time zone detector behavior. + */ + @FunctionalInterface + public interface TimeZoneDetectorListener { + /** + * Called when something about the time zone detector behavior on the device has changed. + * For example, this could be because the current user has switched, one of the global or + * user's settings been changed, or something that could affect a user's capabilities with + * respect to the time zone detector has changed. Because different users can have different + * configuration and capabilities, this method may be called when nothing has changed for + * the receiving user. + */ + void onChange(); + } + + /** + * Registers a listener that will be informed when something about the time zone detector + * behavior changes. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) + public void addTimeZoneDetectorListener(@NonNull Executor executor, + @NonNull TimeZoneDetectorListener listener) { + + if (DEBUG) { + Log.d(TAG, "addTimeZoneDetectorListener called: " + listener); + } + synchronized (mLock) { + if (mTimeZoneDetectorListeners == null) { + mTimeZoneDetectorListeners = new ArrayMap<>(); + } else if (mTimeZoneDetectorListeners.containsKey(listener)) { + return; + } + + if (mTimeZoneDetectorReceiver == null) { + ITimeZoneDetectorListener iListener = new ITimeZoneDetectorListener.Stub() { + @Override + public void onChange() { + notifyTimeZoneDetectorListeners(); + } + }; + mTimeZoneDetectorReceiver = iListener; + try { + mITimeZoneDetectorService.addListener(mTimeZoneDetectorReceiver); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + mTimeZoneDetectorListeners.put(listener, () -> executor.execute(listener::onChange)); + } + } + + private void notifyTimeZoneDetectorListeners() { + ArrayMap<TimeZoneDetectorListener, TimeZoneDetectorListener> timeZoneDetectorListeners; + synchronized (mLock) { + if (mTimeZoneDetectorListeners == null || mTimeZoneDetectorListeners.isEmpty()) { + return; + } + timeZoneDetectorListeners = new ArrayMap<>(mTimeZoneDetectorListeners); + } + int size = timeZoneDetectorListeners.size(); + for (int i = 0; i < size; i++) { + timeZoneDetectorListeners.valueAt(i).onChange(); + } + } + + /** + * Removes a listener previously passed to + * {@link #addTimeZoneDetectorListener(Executor, TimeZoneDetectorListener)} + */ + @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) + public void removeTimeZoneDetectorListener(@NonNull TimeZoneDetectorListener listener) { + if (DEBUG) { + Log.d(TAG, "removeConfigurationListener called: " + listener); + } + + synchronized (mLock) { + if (mTimeZoneDetectorListeners == null || mTimeZoneDetectorListeners.isEmpty()) { + return; + } + mTimeZoneDetectorListeners.remove(listener); + + // If the last local listener has been removed, remove and discard the + // mTimeZoneDetectorReceiver. + if (mTimeZoneDetectorListeners.isEmpty()) { + try { + mITimeZoneDetectorService.removeListener(mTimeZoneDetectorReceiver); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } finally { + mTimeZoneDetectorReceiver = null; + } + } + } + } +} diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl b/core/java/android/app/time/TimeZoneCapabilities.aidl index fede6458318a..f744bf162c67 100644 --- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl +++ b/core/java/android/app/time/TimeZoneCapabilities.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.app.timezonedetector; +package android.app.time; parcelable TimeZoneCapabilities; diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java index 09fffe9f4f25..c62c2b34f35d 100644 --- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java +++ b/core/java/android/app/time/TimeZoneCapabilities.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package android.app.timezonedetector; - -import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED; -import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED; +package android.app.time; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.timezonedetector.ManualTimeZoneSuggestion; +import android.app.timezonedetector.TimeZoneDetector; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -42,15 +42,18 @@ import java.util.Objects; * <p>Actions have associated methods, see the documentation for each action for details. * * <p>For configuration settings capabilities, the associated settings value can be found via - * {@link #getConfiguration()} and may be changed using {@link - * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} (if the user's capabilities allow). + * {@link TimeManager#getTimeZoneCapabilitiesAndConfig()} and may be changed using {@link + * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)} (if the user's capabilities + * allow). * * <p>Note: Capabilities are independent of app permissions required to call the associated APIs. * * @hide */ +// @SystemApi public final class TimeZoneCapabilities implements Parcelable { + /** @hide */ @IntDef({ CAPABILITY_NOT_SUPPORTED, CAPABILITY_NOT_ALLOWED, CAPABILITY_NOT_APPLICABLE, CAPABILITY_POSSESSED }) @Retention(RetentionPolicy.SOURCE) @@ -94,64 +97,60 @@ public final class TimeZoneCapabilities implements Parcelable { } }; - - @NonNull private final TimeZoneConfiguration mConfiguration; - private final @CapabilityState int mConfigureAutoDetectionEnabled; - private final @CapabilityState int mConfigureGeoDetectionEnabled; - private final @CapabilityState int mSuggestManualTimeZone; + /** + * The user the capabilities are for. This is used for object equality and debugging but there + * is no accessor. + */ + @NonNull private final UserHandle mUserHandle; + private final @CapabilityState int mConfigureAutoDetectionEnabledCapability; + private final @CapabilityState int mConfigureGeoDetectionEnabledCapability; + private final @CapabilityState int mSuggestManualTimeZoneCapability; private TimeZoneCapabilities(@NonNull Builder builder) { - this.mConfiguration = Objects.requireNonNull(builder.mConfiguration); - this.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled; - this.mConfigureGeoDetectionEnabled = builder.mConfigureGeoDetectionEnabled; - this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone; + this.mUserHandle = Objects.requireNonNull(builder.mUserHandle); + this.mConfigureAutoDetectionEnabledCapability = + builder.mConfigureAutoDetectionEnabledCapability; + this.mConfigureGeoDetectionEnabledCapability = + builder.mConfigureGeoDetectionEnabledCapability; + this.mSuggestManualTimeZoneCapability = builder.mSuggestManualTimeZoneCapability; } @NonNull private static TimeZoneCapabilities createFromParcel(Parcel in) { - return new TimeZoneCapabilities.Builder() - .setConfiguration(in.readParcelable(null)) - .setConfigureAutoDetectionEnabled(in.readInt()) - .setConfigureGeoDetectionEnabled(in.readInt()) - .setSuggestManualTimeZone(in.readInt()) + UserHandle userHandle = UserHandle.readFromParcel(in); + return new TimeZoneCapabilities.Builder(userHandle) + .setConfigureAutoDetectionEnabledCapability(in.readInt()) + .setConfigureGeoDetectionEnabledCapability(in.readInt()) + .setSuggestManualTimeZoneCapability(in.readInt()) .build(); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeParcelable(mConfiguration, flags); - dest.writeInt(mConfigureAutoDetectionEnabled); - dest.writeInt(mConfigureGeoDetectionEnabled); - dest.writeInt(mSuggestManualTimeZone); - } - - /** - * Returns the user's time zone behavior configuration. - */ - public @NonNull TimeZoneConfiguration getConfiguration() { - return mConfiguration; + UserHandle.writeToParcel(mUserHandle, dest); + dest.writeInt(mConfigureAutoDetectionEnabledCapability); + dest.writeInt(mConfigureGeoDetectionEnabledCapability); + dest.writeInt(mSuggestManualTimeZoneCapability); } /** * Returns the capability state associated with the user's ability to modify the automatic time * zone detection setting. The setting can be updated via {@link - * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link - * #getConfiguration()}. + * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}. */ @CapabilityState - public int getConfigureAutoDetectionEnabled() { - return mConfigureAutoDetectionEnabled; + public int getConfigureAutoDetectionEnabledCapability() { + return mConfigureAutoDetectionEnabledCapability; } /** * Returns the capability state associated with the user's ability to modify the geolocation * detection setting. The setting can be updated via {@link - * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link - * #getConfiguration()}. + * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}. */ @CapabilityState - public int getConfigureGeoDetectionEnabled() { - return mConfigureGeoDetectionEnabled; + public int getConfigureGeoDetectionEnabledCapability() { + return mConfigureGeoDetectionEnabledCapability; } /** @@ -160,36 +159,37 @@ public final class TimeZoneCapabilities implements Parcelable { * * <p>The suggestion will be ignored in all cases unless the value is {@link * #CAPABILITY_POSSESSED}. See also {@link TimeZoneConfiguration#isAutoDetectionEnabled()}. + * + * @hide */ @CapabilityState - public int getSuggestManualTimeZone() { - return mSuggestManualTimeZone; + public int getSuggestManualTimeZoneCapability() { + return mSuggestManualTimeZoneCapability; } /** - * Constructs a new {@link TimeZoneConfiguration} from an {@code oldConfiguration} and a set of - * {@code requestedChanges}, if the current capabilities allow. The new configuration is - * returned and the capabilities are left unchanged. If the capabilities do not permit one or - * more of the changes then {@code null} is returned. + * Tries to create a new {@link TimeZoneConfiguration} from the {@code config} and the set of + * {@code requestedChanges}, if {@code this} capabilities allow. The new configuration is + * returned. If the capabilities do not permit one or more of the requested changes then {@code + * null} is returned. + * + * @hide */ @Nullable - public TimeZoneConfiguration applyUpdate(TimeZoneConfiguration requestedChanges) { - if (requestedChanges.getUserId() != mConfiguration.getUserId()) { - throw new IllegalArgumentException("User does not match:" - + " this=" + mConfiguration + ", other=" + requestedChanges); - } - + public TimeZoneConfiguration tryApplyConfigChanges( + @NonNull TimeZoneConfiguration config, + @NonNull TimeZoneConfiguration requestedChanges) { TimeZoneConfiguration.Builder newConfigBuilder = - new TimeZoneConfiguration.Builder(mConfiguration); - if (requestedChanges.hasSetting(SETTING_AUTO_DETECTION_ENABLED)) { - if (getConfigureAutoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) { + new TimeZoneConfiguration.Builder(config); + if (requestedChanges.hasIsAutoDetectionEnabled()) { + if (this.getConfigureAutoDetectionEnabledCapability() < CAPABILITY_NOT_APPLICABLE) { return null; } newConfigBuilder.setAutoDetectionEnabled(requestedChanges.isAutoDetectionEnabled()); } - if (requestedChanges.hasSetting(SETTING_GEO_DETECTION_ENABLED)) { - if (getConfigureGeoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) { + if (requestedChanges.hasIsGeoDetectionEnabled()) { + if (this.getConfigureGeoDetectionEnabledCapability() < CAPABILITY_NOT_APPLICABLE) { return null; } newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled()); @@ -212,71 +212,71 @@ public final class TimeZoneCapabilities implements Parcelable { return false; } TimeZoneCapabilities that = (TimeZoneCapabilities) o; - return Objects.equals(mConfiguration, that.mConfiguration) - && mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled - && mConfigureGeoDetectionEnabled == that.mConfigureGeoDetectionEnabled - && mSuggestManualTimeZone == that.mSuggestManualTimeZone; + return mUserHandle.equals(that.mUserHandle) + && mConfigureAutoDetectionEnabledCapability + == that.mConfigureAutoDetectionEnabledCapability + && mConfigureGeoDetectionEnabledCapability + == that.mConfigureGeoDetectionEnabledCapability + && mSuggestManualTimeZoneCapability == that.mSuggestManualTimeZoneCapability; } @Override public int hashCode() { - return Objects.hash(mConfiguration, - mConfigureAutoDetectionEnabled, - mConfigureGeoDetectionEnabled, - mSuggestManualTimeZone); + return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability, + mConfigureGeoDetectionEnabledCapability, mSuggestManualTimeZoneCapability); } @Override public String toString() { return "TimeZoneDetectorCapabilities{" - + "mConfiguration=" + mConfiguration - + ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled - + ", mConfigureGeoDetectionEnabled=" + mConfigureGeoDetectionEnabled - + ", mSuggestManualTimeZone=" + mSuggestManualTimeZone + + "mUserHandle=" + mUserHandle + + ", mConfigureAutoDetectionEnabledCapability=" + + mConfigureAutoDetectionEnabledCapability + + ", mConfigureGeoDetectionEnabledCapability=" + + mConfigureGeoDetectionEnabledCapability + + ", mSuggestManualTimeZoneCapability=" + mSuggestManualTimeZoneCapability + '}'; } /** @hide */ public static class Builder { - private TimeZoneConfiguration mConfiguration; - private @CapabilityState int mConfigureAutoDetectionEnabled; - private @CapabilityState int mConfigureGeoDetectionEnabled; - private @CapabilityState int mSuggestManualTimeZone; + @NonNull private UserHandle mUserHandle; + private @CapabilityState int mConfigureAutoDetectionEnabledCapability; + private @CapabilityState int mConfigureGeoDetectionEnabledCapability; + private @CapabilityState int mSuggestManualTimeZoneCapability; - /** Sets the user-visible configuration settings. */ - public Builder setConfiguration(@NonNull TimeZoneConfiguration configuration) { - if (!configuration.isComplete()) { - throw new IllegalArgumentException(configuration + " is not complete"); - } - this.mConfiguration = configuration; - return this; + public Builder(@NonNull UserHandle userHandle) { + mUserHandle = Objects.requireNonNull(userHandle); } /** Sets the state for the automatic time zone detection enabled config. */ - public Builder setConfigureAutoDetectionEnabled(@CapabilityState int value) { - this.mConfigureAutoDetectionEnabled = value; + public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) { + this.mConfigureAutoDetectionEnabledCapability = value; return this; } /** Sets the state for the geolocation time zone detection enabled config. */ - public Builder setConfigureGeoDetectionEnabled(@CapabilityState int value) { - this.mConfigureGeoDetectionEnabled = value; + public Builder setConfigureGeoDetectionEnabledCapability(@CapabilityState int value) { + this.mConfigureGeoDetectionEnabledCapability = value; return this; } /** Sets the state for the suggestManualTimeZone action. */ - public Builder setSuggestManualTimeZone(@CapabilityState int value) { - this.mSuggestManualTimeZone = value; + public Builder setSuggestManualTimeZoneCapability(@CapabilityState int value) { + this.mSuggestManualTimeZoneCapability = value; return this; } /** Returns the {@link TimeZoneCapabilities}. */ @NonNull public TimeZoneCapabilities build() { - verifyCapabilitySet(mConfigureAutoDetectionEnabled, "configureAutoDetectionEnabled"); - verifyCapabilitySet(mConfigureGeoDetectionEnabled, "configureGeoDetectionEnabled"); - verifyCapabilitySet(mSuggestManualTimeZone, "suggestManualTimeZone"); + verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability, + "configureAutoDetectionEnabledCapability"); + verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability, + "configureGeoDetectionEnabledCapability"); + verifyCapabilitySet(mSuggestManualTimeZoneCapability, + "suggestManualTimeZoneCapability"); return new TimeZoneCapabilities(this); } diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.aidl b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.aidl new file mode 100644 index 000000000000..d7b6b58bf85a --- /dev/null +++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.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.app.time; + +parcelable TimeZoneCapabilitiesAndConfig; diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java new file mode 100644 index 000000000000..6a04f3f277ed --- /dev/null +++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.time; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * A pair containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}. + * + * @hide + */ +// @SystemApi +public final class TimeZoneCapabilitiesAndConfig implements Parcelable { + + public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR = + new Creator<TimeZoneCapabilitiesAndConfig>() { + public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) { + return TimeZoneCapabilitiesAndConfig.createFromParcel(in); + } + + public TimeZoneCapabilitiesAndConfig[] newArray(int size) { + return new TimeZoneCapabilitiesAndConfig[size]; + } + }; + + + @NonNull private final TimeZoneCapabilities mCapabilities; + @NonNull private final TimeZoneConfiguration mConfiguration; + + /** + * Creates a new instance. + * + * @hide + */ + public TimeZoneCapabilitiesAndConfig( + @NonNull TimeZoneCapabilities capabilities, + @NonNull TimeZoneConfiguration configuration) { + this.mCapabilities = Objects.requireNonNull(capabilities); + this.mConfiguration = Objects.requireNonNull(configuration); + } + + @NonNull + private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) { + TimeZoneCapabilities capabilities = in.readParcelable(null); + TimeZoneConfiguration configuration = in.readParcelable(null); + return new TimeZoneCapabilitiesAndConfig(capabilities, configuration); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mCapabilities, flags); + dest.writeParcelable(mConfiguration, flags); + } + + /** + * Returns the user's time zone behavior capabilities. + */ + @NonNull + public TimeZoneCapabilities getCapabilities() { + return mCapabilities; + } + + /** + * Returns the user's time zone behavior configuration. + */ + @NonNull + public TimeZoneConfiguration getConfiguration() { + return mConfiguration; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TimeZoneCapabilitiesAndConfig that = (TimeZoneCapabilitiesAndConfig) o; + return mCapabilities.equals(that.mCapabilities) + && mConfiguration.equals(that.mConfiguration); + } + + @Override + public int hashCode() { + return Objects.hash(mCapabilities, mConfiguration); + } + + @Override + public String toString() { + return "TimeZoneDetectorCapabilitiesAndConfig{" + + "mCapabilities=" + mCapabilities + + ", mConfiguration=" + mConfiguration + + '}'; + } +} diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl b/core/java/android/app/time/TimeZoneConfiguration.aidl index 62240ba5946b..8e859299d073 100644 --- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl +++ b/core/java/android/app/time/TimeZoneConfiguration.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.app.timezonedetector; +package android.app.time; parcelable TimeZoneConfiguration; diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java b/core/java/android/app/time/TimeZoneConfiguration.java index e879091cd68e..488818a528ef 100644 --- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java +++ b/core/java/android/app/time/TimeZoneConfiguration.java @@ -14,11 +14,10 @@ * limitations under the License. */ -package android.app.timezonedetector; +package android.app.time; import android.annotation.NonNull; import android.annotation.StringDef; -import android.annotation.UserIdInt; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -36,15 +35,13 @@ import java.util.Objects; * several settings, the device behavior may not be directly affected by the setting value. * * <p>Settings can be left absent when updating configuration via {@link - * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and those settings will not be + * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)} and those settings will not be * changed. Not all configuration settings can be modified by all users: see {@link - * TimeZoneDetector#getCapabilities()} and {@link TimeZoneCapabilities} for details. - * - * <p>See {@link #hasSetting(String)} with {@code PROPERTY_} constants for testing for the presence - * of individual settings. + * TimeManager#getTimeZoneCapabilitiesAndConfig()} and {@link TimeZoneCapabilities} for details. * * @hide */ +// @SystemApi public final class TimeZoneConfiguration implements Parcelable { public static final @NonNull Creator<TimeZoneConfiguration> CREATOR = @@ -58,53 +55,48 @@ public final class TimeZoneConfiguration implements Parcelable { } }; - /** All configuration properties */ + /** + * All configuration properties + * + * @hide + */ @StringDef({ SETTING_AUTO_DETECTION_ENABLED, SETTING_GEO_DETECTION_ENABLED }) @Retention(RetentionPolicy.SOURCE) @interface Setting {} /** See {@link TimeZoneConfiguration#isAutoDetectionEnabled()} for details. */ @Setting - public static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled"; + private static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled"; /** See {@link TimeZoneConfiguration#isGeoDetectionEnabled()} for details. */ @Setting - public static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled"; + private static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled"; - private final @UserIdInt int mUserId; @NonNull private final Bundle mBundle; private TimeZoneConfiguration(Builder builder) { - this.mUserId = builder.mUserId; this.mBundle = Objects.requireNonNull(builder.mBundle); } private static TimeZoneConfiguration createFromParcel(Parcel in) { - return new TimeZoneConfiguration.Builder(in.readInt()) + return new TimeZoneConfiguration.Builder() .setPropertyBundleInternal(in.readBundle()) .build(); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mUserId); dest.writeBundle(mBundle); } - /** Returns the ID of the user this configuration is associated with. */ - public @UserIdInt int getUserId() { - return mUserId; - } - - /** Returns {@code true} if all known settings are present. */ + /** + * Returns {@code true} if all known settings are present. + * + * @hide + */ public boolean isComplete() { - return hasSetting(SETTING_AUTO_DETECTION_ENABLED) - && hasSetting(SETTING_GEO_DETECTION_ENABLED); - } - - /** Returns true if the specified setting is set. */ - public boolean hasSetting(@Setting String setting) { - return mBundle.containsKey(setting); + return hasIsAutoDetectionEnabled() + && hasIsGeoDetectionEnabled(); } /** @@ -112,9 +104,10 @@ public final class TimeZoneConfiguration implements Parcelable { * controls whether a device will attempt to determine the time zone automatically using * contextual information if the device supports auto detection. * - * <p>This setting is global and can be updated by some users. + * <p>See {@link TimeZoneCapabilities#getConfigureAutoDetectionEnabledCapability()} for how to + * tell if the setting is meaningful for the current user at this time. * - * @throws IllegalStateException if the setting has not been set + * @throws IllegalStateException if the setting is not present */ public boolean isAutoDetectionEnabled() { enforceSettingPresent(SETTING_AUTO_DETECTION_ENABLED); @@ -122,21 +115,39 @@ public final class TimeZoneConfiguration implements Parcelable { } /** + * Returns {@code true} if the {@link #isAutoDetectionEnabled()} setting is present. + * + * @hide + */ + public boolean hasIsAutoDetectionEnabled() { + return mBundle.containsKey(SETTING_AUTO_DETECTION_ENABLED); + } + + /** * Returns the value of the {@link #SETTING_GEO_DETECTION_ENABLED} setting. This - * controls whether a device can use geolocation to determine time zone. Only used when - * {@link #isAutoDetectionEnabled()} is {@code true} and when the user has allowed their - * location to be used. + * controls whether the device can use geolocation to determine time zone. This value may only + * be used by Android under some circumstances. For example, it is not used when + * {@link #isGeoDetectionEnabled()} is {@code false}. * - * <p>This setting is user-scoped and can be updated by some users. - * See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabled()}. + * <p>See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabledCapability()} for how to + * tell if the setting is meaningful for the current user at this time. * - * @throws IllegalStateException if the setting has not been set + * @throws IllegalStateException if the setting is not present */ public boolean isGeoDetectionEnabled() { enforceSettingPresent(SETTING_GEO_DETECTION_ENABLED); return mBundle.getBoolean(SETTING_GEO_DETECTION_ENABLED); } + /** + * Returns {@code true} if the {@link #isGeoDetectionEnabled()} setting is present. + * + * @hide + */ + public boolean hasIsGeoDetectionEnabled() { + return mBundle.containsKey(SETTING_GEO_DETECTION_ENABLED); + } + @Override public int describeContents() { return 0; @@ -151,20 +162,18 @@ public final class TimeZoneConfiguration implements Parcelable { return false; } TimeZoneConfiguration that = (TimeZoneConfiguration) o; - return mUserId == that.mUserId - && mBundle.kindofEquals(that.mBundle); + return mBundle.kindofEquals(that.mBundle); } @Override public int hashCode() { - return Objects.hash(mUserId, mBundle); + return Objects.hash(mBundle); } @Override public String toString() { return "TimeZoneConfiguration{" - + "mUserId=" + mUserId - + ", mBundle=" + mBundle + + "mBundle=" + mBundle + '}'; } @@ -174,43 +183,43 @@ public final class TimeZoneConfiguration implements Parcelable { } } - /** @hide */ - public static class Builder { + /** + * A builder for {@link TimeZoneConfiguration} objects. + * + * @hide + */ + // @SystemApi + public static final class Builder { - private final @UserIdInt int mUserId; private final Bundle mBundle = new Bundle(); /** - * Creates a new Builder for a userId with no settings held. + * Creates a new Builder with no settings held. */ - public Builder(@UserIdInt int userId) { - mUserId = userId; + public Builder() { } /** - * Creates a new Builder by copying the user ID and settings from an existing instance. + * Creates a new Builder by copying the settings from an existing instance. */ - public Builder(TimeZoneConfiguration toCopy) { - this.mUserId = toCopy.mUserId; + public Builder(@NonNull TimeZoneConfiguration toCopy) { mergeProperties(toCopy); } /** * Merges {@code other} settings into this instances, replacing existing values in this * where the settings appear in both. + * + * @hide */ - public Builder mergeProperties(TimeZoneConfiguration other) { - if (mUserId != other.mUserId) { - throw new IllegalArgumentException( - "Cannot merge configurations for different user IDs." - + " this.mUserId=" + this.mUserId - + ", other.mUserId=" + other.mUserId); - } + @NonNull + public Builder mergeProperties(@NonNull TimeZoneConfiguration other) { this.mBundle.putAll(other.mBundle); return this; } - Builder setPropertyBundleInternal(Bundle bundle) { + @NonNull + Builder setPropertyBundleInternal(@NonNull Bundle bundle) { this.mBundle.putAll(bundle); return this; } @@ -218,6 +227,7 @@ public final class TimeZoneConfiguration implements Parcelable { /** * Sets the state of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting. */ + @NonNull public Builder setAutoDetectionEnabled(boolean enabled) { this.mBundle.putBoolean(SETTING_AUTO_DETECTION_ENABLED, enabled); return this; @@ -226,6 +236,7 @@ public final class TimeZoneConfiguration implements Parcelable { /** * Sets the state of the {@link #SETTING_GEO_DETECTION_ENABLED} setting. */ + @NonNull public Builder setGeoDetectionEnabled(boolean enabled) { this.mBundle.putBoolean(SETTING_GEO_DETECTION_ENABLED, enabled); return this; diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl index 4f7e1f62928a..af0389a14c4b 100644 --- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl +++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl @@ -16,11 +16,11 @@ package android.app.timezonedetector; -import android.app.timezonedetector.ITimeZoneConfigurationListener; +import android.app.time.ITimeZoneDetectorListener; +import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneConfiguration; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; -import android.app.timezonedetector.TimeZoneCapabilities; -import android.app.timezonedetector.TimeZoneConfiguration; /** * System private API to communicate with time zone detector service. @@ -35,9 +35,9 @@ import android.app.timezonedetector.TimeZoneConfiguration; * {@hide} */ interface ITimeZoneDetectorService { - TimeZoneCapabilities getCapabilities(); - void addConfigurationListener(ITimeZoneConfigurationListener listener); - void removeConfigurationListener(ITimeZoneConfigurationListener listener); + TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(); + void addListener(ITimeZoneDetectorListener listener); + void removeListener(ITimeZoneDetectorListener listener); boolean updateConfiguration(in TimeZoneConfiguration configuration); diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java index 2b1cbf259c55..486232d0f6ed 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -30,70 +30,6 @@ import android.content.Context; public interface TimeZoneDetector { /** - * Returns the current user's time zone capabilities. See {@link TimeZoneCapabilities}. - */ - @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) - @NonNull - TimeZoneCapabilities getCapabilities(); - - /** - * Modifies the time zone detection configuration. - * - * <p>Configuration settings vary in scope: some may be global (affect all users), others may be - * specific to the current user. - * - * <p>The ability to modify configuration settings can be subject to restrictions. For - * example, they may be determined by device hardware, general policy (i.e. only the primary - * user can set them), or by a managed device policy. Use {@link #getCapabilities()} to obtain - * information at runtime about the user's capabilities. - * - * <p>Attempts to modify configuration settings with capabilities that are {@link - * TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link - * TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false} - * will be returned. Modifying configuration settings with capabilities that are {@link - * TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link - * TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link - * TimeZoneCapabilities} for further details. - * - * <p>If the supplied configuration only has some values set, then only the specified settings - * will be updated (where the user's capabilities allow) and other settings will be left - * unchanged. - * - * @return {@code true} if all the configuration settings specified have been set to the - * new values, {@code false} if none have - */ - @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) - boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration); - - /** - * An interface that can be used to listen for changes to the time zone detector configuration. - */ - @FunctionalInterface - interface TimeZoneConfigurationListener { - /** - * Called when something about the time zone configuration on the device has changed. - * This could be because the current user has changed, one of the device's relevant settings - * has changed, or something that could affect a user's capabilities has changed. - * There are no guarantees about the thread used. - */ - void onChange(); - } - - /** - * Registers a listener that will be informed when something about the time zone configuration - * changes. - */ - @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) - void addConfigurationListener(@NonNull TimeZoneConfigurationListener listener); - - /** - * Removes a listener previously passed to - * {@link #addConfigurationListener(ITimeZoneConfigurationListener)} - */ - @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) - void removeConfigurationListener(@NonNull TimeZoneConfigurationListener listener); - - /** * A shared utility method to create a {@link ManualTimeZoneSuggestion}. * * @hide diff --git a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java index 4c69732abec9..3bd6b4bd692a 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java @@ -21,7 +21,6 @@ import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; -import android.util.ArraySet; import android.util.Log; /** @@ -35,108 +34,12 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector { private final ITimeZoneDetectorService mITimeZoneDetectorService; - private ITimeZoneConfigurationListener mConfigurationReceiver; - private ArraySet<TimeZoneConfigurationListener> mConfigurationListeners; - public TimeZoneDetectorImpl() throws ServiceNotFoundException { mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE)); } @Override - @NonNull - public TimeZoneCapabilities getCapabilities() { - if (DEBUG) { - Log.d(TAG, "getCapabilities called"); - } - try { - return mITimeZoneDetectorService.getCapabilities(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - @Override - public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) { - if (DEBUG) { - Log.d(TAG, "updateConfiguration called: " + configuration); - } - try { - return mITimeZoneDetectorService.updateConfiguration(configuration); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - @Override - public void addConfigurationListener(@NonNull TimeZoneConfigurationListener listener) { - if (DEBUG) { - Log.d(TAG, "addConfigurationListener called: " + listener); - } - synchronized (this) { - if (mConfigurationListeners.contains(listener)) { - return; - } - if (mConfigurationReceiver == null) { - ITimeZoneConfigurationListener iListener = - new ITimeZoneConfigurationListener.Stub() { - @Override - public void onChange() { - notifyConfigurationListeners(); - } - }; - mConfigurationReceiver = iListener; - } - if (mConfigurationListeners == null) { - mConfigurationListeners = new ArraySet<>(); - } - - boolean wasEmpty = mConfigurationListeners.isEmpty(); - mConfigurationListeners.add(listener); - if (wasEmpty) { - try { - mITimeZoneDetectorService.addConfigurationListener(mConfigurationReceiver); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - } - } - - private void notifyConfigurationListeners() { - final ArraySet<TimeZoneConfigurationListener> configurationListeners; - synchronized (this) { - configurationListeners = new ArraySet<>(mConfigurationListeners); - } - int size = configurationListeners.size(); - for (int i = 0; i < size; i++) { - configurationListeners.valueAt(i).onChange(); - } - } - - @Override - public void removeConfigurationListener(@NonNull TimeZoneConfigurationListener listener) { - if (DEBUG) { - Log.d(TAG, "removeConfigurationListener called: " + listener); - } - - synchronized (this) { - if (mConfigurationListeners == null) { - return; - } - boolean wasEmpty = mConfigurationListeners.isEmpty(); - mConfigurationListeners.remove(listener); - if (mConfigurationListeners.isEmpty() && !wasEmpty) { - try { - mITimeZoneDetectorService.removeConfigurationListener(mConfigurationReceiver); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - } - } - - @Override public boolean suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) { if (DEBUG) { Log.d(TAG, "suggestManualTimeZone called: " + timeZoneSuggestion); diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java index 9f6b8287e791..1c40cff076f6 100644 --- a/core/java/android/bluetooth/BluetoothGattCallback.java +++ b/core/java/android/bluetooth/BluetoothGattCallback.java @@ -16,6 +16,8 @@ package android.bluetooth; +import android.annotation.NonNull; + /** * This abstract class is used to implement {@link BluetoothGatt} callbacks. */ @@ -203,8 +205,7 @@ public abstract class BluetoothGattCallback { * called to re-discover the services. * * @param gatt GATT client involved - * @hide */ - public void onServiceChanged(BluetoothGatt gatt) { + public void onServiceChanged(@NonNull BluetoothGatt gatt) { } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 005648ffec36..666ba32d0e4f 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.time.TimeManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -5090,6 +5091,14 @@ public abstract class Context { public static final String TIME_ZONE_DETECTOR_SERVICE = "time_zone_detector"; /** + * Use with {@link #getSystemService(String)} to retrieve an {@link TimeManager}. + * @hide + * + * @see #getSystemService(String) + */ + public static final String TIME_MANAGER = "time_manager"; + + /** * Binder service name for {@link AppBindingService}. * @hide */ diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 24282365a8c7..ba894ae72017 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -795,4 +795,6 @@ interface IPackageManager { boolean isAutoRevokeWhitelisted(String packageName); void grantImplicitAccess(int queryingUid, String visibleAuthority); + + void holdLock(in int durationMs); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 4eec56c0d87b..a600d6c8a236 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -866,6 +866,12 @@ public abstract class PackageManager { * is set the restricted permissions will be whitelisted for all users, otherwise * only to the owner. * + * <p> + * <strong>Note: </strong>In retrospect it would have been preferred to use + * more inclusive terminology when naming this API. Similar APIs added will + * refrain from using the term "whitelist". + * </p> + * * @hide */ public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00400000; @@ -3505,6 +3511,17 @@ public abstract class PackageManager { public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 17; /** + * Permission flag: The permission is restricted but the app is exempt + * from the restriction and is allowed to hold this permission in its + * full form and the exemption is provided by the held roles. + * + * @hide + */ + @TestApi + @SystemApi + public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 1 << 18; + + /** * Permission flags: Reserved for use by the permission controller. The platform and any * packages besides the permission controller should not assume any definition about these * flags. @@ -3522,7 +3539,8 @@ public abstract class PackageManager { public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT = FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT | FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT - | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; + | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT + | FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; /** * Mask for all permission flags. @@ -3568,13 +3586,27 @@ public abstract class PackageManager { /** * Permission whitelist flag: permissions whitelisted by the system. - * Permissions can also be whitelisted by the installer or on upgrade. + * Permissions can also be whitelisted by the installer, on upgrade, or on + * role grant. + * + * <p> + * <strong>Note: </strong>In retrospect it would have been preferred to use + * more inclusive terminology when naming this API. Similar APIs added will + * refrain from using the term "whitelist". + * </p> */ public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1 << 0; /** * Permission whitelist flag: permissions whitelisted by the installer. - * Permissions can also be whitelisted by the system or on upgrade. + * Permissions can also be whitelisted by the system, on upgrade, or on role + * grant. + * + * <p> + * <strong>Note: </strong>In retrospect it would have been preferred to use + * more inclusive terminology when naming this API. Similar APIs added will + * refrain from using the term "whitelist". + * </p> */ public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 1 << 1; @@ -3582,15 +3614,31 @@ public abstract class PackageManager { * Permission whitelist flag: permissions whitelisted by the system * when upgrading from an OS version where the permission was not * restricted to an OS version where the permission is restricted. - * Permissions can also be whitelisted by the installer or the system. + * Permissions can also be whitelisted by the installer, the system, or on + * role grant. + * + * <p> + * <strong>Note: </strong>In retrospect it would have been preferred to use + * more inclusive terminology when naming this API. Similar APIs added will + * refrain from using the term "whitelist". + * </p> */ public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 1 << 2; + /** + * Permission allowlist flag: permissions exempted by the system + * when being granted a role. + * Permissions can also be exempted by the installer, the system, or on + * upgrade. + */ + public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 1 << 3; + /** @hide */ @IntDef(flag = true, prefix = {"FLAG_PERMISSION_WHITELIST_"}, value = { FLAG_PERMISSION_WHITELIST_SYSTEM, FLAG_PERMISSION_WHITELIST_INSTALLER, - FLAG_PERMISSION_WHITELIST_UPGRADE + FLAG_PERMISSION_WHITELIST_UPGRADE, + FLAG_PERMISSION_ALLOWLIST_ROLE }) @Retention(RetentionPolicy.SOURCE) public @interface PermissionWhitelistFlags {} @@ -4536,7 +4584,7 @@ public abstract class PackageManager { * allows for the to hold that permission and whitelisting a soft restricted * permission allows the app to hold the permission in its full, unrestricted form. * - * <p><ol>There are three whitelists: + * <p><ol>There are four allowlists: * * <li>one for cases where the system permission policy whitelists a permission * This list corresponds to the{@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag. @@ -4553,6 +4601,17 @@ public abstract class PackageManager { * Can be accessed by pre-installed holders of a dedicated permission or the * installer on record. * + * <li>one for cases where the system exempts the permission when granting a role. + * This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can + * be accessed by pre-installed holders of a dedicated permission. + * </ol> + * + * <p> + * <strong>Note: </strong>In retrospect it would have been preferred to use + * more inclusive terminology when naming this API. Similar APIs added will + * refrain from using the term "whitelist". + * </p> + * * @param packageName The app for which to get whitelisted permissions. * @param whitelistFlag The flag to determine which whitelist to query. Only one flag * can be passed.s @@ -4563,6 +4622,7 @@ public abstract class PackageManager { * @see #FLAG_PERMISSION_WHITELIST_SYSTEM * @see #FLAG_PERMISSION_WHITELIST_UPGRADE * @see #FLAG_PERMISSION_WHITELIST_INSTALLER + * @see #FLAG_PERMISSION_ALLOWLIST_ROLE * * @throws SecurityException if you try to access a whitelist that you have no access to. */ @@ -4584,7 +4644,7 @@ public abstract class PackageManager { * allows for the to hold that permission and whitelisting a soft restricted * permission allows the app to hold the permission in its full, unrestricted form. * - * <p><ol>There are three whitelists: + * <p><ol>There are four whitelists: * * <li>one for cases where the system permission policy whitelists a permission * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag. @@ -4602,10 +4662,21 @@ public abstract class PackageManager { * Can be modified by pre-installed holders of a dedicated permission or the installer * on record. * + * <li>one for cases where the system exempts the permission when permission when + * granting a role. This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE} + * flag. Can be modified by pre-installed holders of a dedicated permission. + * </ol> + * * <p>You need to specify the whitelists for which to set the whitelisted permissions * which will clear the previous whitelisted permissions and replace them with the * provided ones. * + * <p> + * <strong>Note: </strong>In retrospect it would have been preferred to use + * more inclusive terminology when naming this API. Similar APIs added will + * refrain from using the term "whitelist". + * </p> + * * @param packageName The app for which to get whitelisted permissions. * @param permName The whitelisted permission to add. * @param whitelistFlags The whitelists to which to add. Passing multiple flags @@ -4617,6 +4688,7 @@ public abstract class PackageManager { * @see #FLAG_PERMISSION_WHITELIST_SYSTEM * @see #FLAG_PERMISSION_WHITELIST_UPGRADE * @see #FLAG_PERMISSION_WHITELIST_INSTALLER + * @see #FLAG_PERMISSION_ALLOWLIST_ROLE * * @throws SecurityException if you try to modify a whitelist that you have no access to. */ @@ -4638,7 +4710,7 @@ public abstract class PackageManager { * allows for the to hold that permission and whitelisting a soft restricted * permission allows the app to hold the permission in its full, unrestricted form. * - * <p><ol>There are three whitelists: + * <p><ol>There are four whitelists: * * <li>one for cases where the system permission policy whitelists a permission * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag. @@ -4656,10 +4728,24 @@ public abstract class PackageManager { * Can be modified by pre-installed holders of a dedicated permission or the installer * on record. * + * <li>one for cases where the system exempts the permission when upgrading + * from an OS version in which the permission was not restricted to an OS version + * in which the permission is restricted. This list corresponds to the {@link + * #FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be modified by pre-installed + * holders of a dedicated permission. The installer on record can only remove + * permissions from this allowlist. + * </ol> + * * <p>You need to specify the whitelists for which to set the whitelisted permissions * which will clear the previous whitelisted permissions and replace them with the * provided ones. * + * <p> + * <strong>Note: </strong>In retrospect it would have been preferred to use + * more inclusive terminology when naming this API. Similar APIs added will + * refrain from using the term "whitelist". + * </p> + * * @param packageName The app for which to get whitelisted permissions. * @param permName The whitelisted permission to remove. * @param whitelistFlags The whitelists from which to remove. Passing multiple flags @@ -4671,6 +4757,7 @@ public abstract class PackageManager { * @see #FLAG_PERMISSION_WHITELIST_SYSTEM * @see #FLAG_PERMISSION_WHITELIST_UPGRADE * @see #FLAG_PERMISSION_WHITELIST_INSTALLER + * @see #FLAG_PERMISSION_ALLOWLIST_ROLE * * @throws SecurityException if you try to modify a whitelist that you have no access to. */ @@ -4691,6 +4778,12 @@ public abstract class PackageManager { * un-whitelist the packages it installs, unless auto-revoking permissions from that package * would cause breakages beyond having to re-request the permission(s). * + * <p> + * <strong>Note: </strong>In retrospect it would have been preferred to use + * more inclusive terminology when naming this API. Similar APIs added will + * refrain from using the term "whitelist". + * </p> + * * @param packageName The app for which to set exemption. * @param whitelisted Whether the app should be whitelisted. * @@ -4712,6 +4805,13 @@ public abstract class PackageManager { * * Only the installer on record that installed the given package, or a holder of * {@code WHITELIST_AUTO_REVOKE_PERMISSIONS} is allowed to call this. + * + * <p> + * <strong>Note: </strong>In retrospect it would have been preferred to use + * more inclusive terminology when naming this API. Similar APIs added will + * refrain from using the term "whitelist". + * </p> + * * @param packageName The app for which to set exemption. * * @return Whether the app is whitelisted. @@ -8026,6 +8126,12 @@ public abstract class PackageManager { } /** + * <p> + * <strong>Note: </strong>In retrospect it would have been preferred to use + * more inclusive terminology when naming this API. Similar APIs added will + * refrain from using the term "whitelist". + * </p> + * * @return whether this package is whitelisted from having its runtime permission be * auto-revoked if unused for an extended period of time. */ @@ -8305,4 +8411,19 @@ public abstract class PackageManager { public static void uncorkPackageInfoCache() { PropertyInvalidatedCache.uncorkInvalidations(PermissionManager.CACHE_KEY_PACKAGE_INFO); } + + /** + * Holds the PM lock for the specified amount of milliseconds. + * Intended for use by the tests that need to imitate lock contention. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) + public void holdLock(int durationMs) { + try { + ActivityThread.getPackageManager().holdLock(durationMs); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index 04e15c20b2f4..5d4c843d2eab 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -377,6 +377,14 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_IMMUTABLY_RESTRICTED = 1<<4; /** + * Flag for {@link #flags}, corresponding to <code>installerExemptIgnored</code> + * value of {@link android.R.attr#permissionFlags}. + * + * <p> Modifier for permission restriction. This permission cannot be exempted by the installer. + */ + public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 1 << 5; + + /** * Flag for {@link #flags}, indicating that this permission has been * installed into the system's globally defined permissions. */ @@ -656,6 +664,11 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { } /** @hide */ + public boolean isInstallerExemptIgnored() { + return (flags & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0; + } + + /** @hide */ public boolean isAppOp() { return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0; } diff --git a/core/java/android/hardware/biometrics/SensorProperties.java b/core/java/android/hardware/biometrics/SensorProperties.java index 0f33ac4f0568..b3dcb8f70532 100644 --- a/core/java/android/hardware/biometrics/SensorProperties.java +++ b/core/java/android/hardware/biometrics/SensorProperties.java @@ -17,66 +17,71 @@ package android.hardware.biometrics; import android.annotation.IntDef; -import android.os.Parcel; -import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * The base class containing all sensor-agnostic information. This is a superset of the - * {@link android.hardware.biometrics.common.CommonProps}, and provides backwards-compatible - * behavior with the older generation of HIDL (non-AIDL) interfaces. + * The base class containing all modality-agnostic information. * @hide */ -public class SensorProperties implements Parcelable { - +public class SensorProperties { + /** + * A sensor that meets the requirements for Class 1 biometrics as defined in the CDD. This does + * not correspond to a public BiometricManager.Authenticators constant. Sensors of this strength + * are not available to applications via the public API surface. + * @hide + */ public static final int STRENGTH_CONVENIENCE = 0; + + /** + * A sensor that meets the requirements for Class 2 biometrics as defined in the CDD. + * Corresponds to BiometricManager.Authenticators.BIOMETRIC_WEAK. + * @hide + */ public static final int STRENGTH_WEAK = 1; + + /** + * A sensor that meets the requirements for Class 3 biometrics as defined in the CDD. + * Corresponds to BiometricManager.Authenticators.BIOMETRIC_STRONG. + * + * Notably, this is the only strength that allows generation of HardwareAuthToken(s). + * @hide + */ public static final int STRENGTH_STRONG = 2; + /** + * @hide + */ @IntDef({STRENGTH_CONVENIENCE, STRENGTH_WEAK, STRENGTH_STRONG}) @Retention(RetentionPolicy.SOURCE) public @interface Strength {} - public final int sensorId; - @Strength public final int sensorStrength; - public final int maxEnrollmentsPerUser; + private final int mSensorId; + @Strength private final int mSensorStrength; - protected SensorProperties(int sensorId, @Strength int sensorStrength, - int maxEnrollmentsPerUser) { - this.sensorId = sensorId; - this.sensorStrength = sensorStrength; - this.maxEnrollmentsPerUser = maxEnrollmentsPerUser; + /** + * @hide + */ + public SensorProperties(int sensorId, @Strength int sensorStrength) { + mSensorId = sensorId; + mSensorStrength = sensorStrength; } - protected SensorProperties(Parcel in) { - sensorId = in.readInt(); - sensorStrength = in.readInt(); - maxEnrollmentsPerUser = in.readInt(); - } - - public static final Creator<SensorProperties> CREATOR = new Creator<SensorProperties>() { - @Override - public SensorProperties createFromParcel(Parcel in) { - return new SensorProperties(in); - } - - @Override - public SensorProperties[] newArray(int size) { - return new SensorProperties[size]; - } - }; - - @Override - public int describeContents() { - return 0; + /** + * @return The sensor's unique identifier. + * @hide + */ + public int getSensorId() { + return mSensorId; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(sensorId); - dest.writeInt(sensorStrength); - dest.writeInt(maxEnrollmentsPerUser); + /** + * @return The sensor's strength. + * @hide + */ + @Strength + public int getSensorStrength() { + return mSensorStrength; } } diff --git a/core/java/android/hardware/biometrics/SensorPropertiesInternal.java b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java new file mode 100644 index 000000000000..2189de0827b7 --- /dev/null +++ b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java @@ -0,0 +1,75 @@ +/* + * 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.hardware.biometrics; + +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * The base class containing all modality-agnostic information. This is a superset of the + * {@link android.hardware.biometrics.common.CommonProps}, and provides backwards-compatible + * behavior with the older generation of HIDL (non-AIDL) interfaces. + * @hide + */ +public class SensorPropertiesInternal implements Parcelable { + + public final int sensorId; + @SensorProperties.Strength public final int sensorStrength; + public final int maxEnrollmentsPerUser; + + protected SensorPropertiesInternal(int sensorId, @SensorProperties.Strength int sensorStrength, + int maxEnrollmentsPerUser) { + this.sensorId = sensorId; + this.sensorStrength = sensorStrength; + this.maxEnrollmentsPerUser = maxEnrollmentsPerUser; + } + + protected SensorPropertiesInternal(Parcel in) { + sensorId = in.readInt(); + sensorStrength = in.readInt(); + maxEnrollmentsPerUser = in.readInt(); + } + + public static final Creator<SensorPropertiesInternal> CREATOR = + new Creator<SensorPropertiesInternal>() { + @Override + public SensorPropertiesInternal createFromParcel(Parcel in) { + return new SensorPropertiesInternal(in); + } + + @Override + public SensorPropertiesInternal[] newArray(int size) { + return new SensorPropertiesInternal[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(sensorId); + dest.writeInt(sensorStrength); + dest.writeInt(maxEnrollmentsPerUser); + } +} diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 1061121b4449..0d0bfb32e293 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -2175,10 +2175,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> /** * <p>The desired zoom ratio</p> - * <p>Instead of using {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} with dual purposes of crop and zoom, the - * application can now choose to use this tag to specify the desired zoom level. The - * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} can still be used to specify the horizontal or vertical - * crop to achieve aspect ratios different than the native camera sensor.</p> + * <p>Instead of using {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} for zoom, the application can now choose to + * use this tag to specify the desired zoom level.</p> * <p>By using this control, the application gains a simpler way to control zoom, which can * be a combination of optical and digital zoom. For example, a multi-camera system may * contain more than one lens with different focal lengths, and the user can use optical @@ -2860,11 +2858,18 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * respectively.</p> * <p>The camera device may adjust the crop region to account for rounding and other hardware * requirements; the final crop region used will be included in the output capture result.</p> + * <p>The camera sensor output aspect ratio depends on factors such as output stream + * combination and {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}, and shouldn't be adjusted by using + * this control. And the camera device will treat different camera sensor output sizes + * (potentially with in-sensor crop) as the same crop of + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}. As a result, the application shouldn't assume the + * maximum crop region always maps to the same aspect ratio or field of view for the + * sensor output.</p> * <p>Starting from API level 30, it's strongly recommended to use {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} * to take advantage of better support for zoom with logical multi-camera. The benefits * include better precision with optical-digital zoom combination, and ability to do * zoom-out from 1.0x. When using {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for zoom, the crop region in - * the capture request must be either letterboxing or pillarboxing (but not both). The + * the capture request should be left as the default activeArray size. The * coordinate system is post-zoom, meaning that the activeArraySize or * preCorrectionActiveArraySize covers the camera device's field of view "after" zoom. See * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p> @@ -2874,6 +2879,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * capability and mode</p> * <p>This key is available on all devices.</p> * + * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE * @see CaptureRequest#CONTROL_ZOOM_RATIO * @see CaptureRequest#DISTORTION_CORRECTION_MODE * @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 29a53fb6d4a2..8cfa0866f13a 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2405,10 +2405,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * <p>The desired zoom ratio</p> - * <p>Instead of using {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} with dual purposes of crop and zoom, the - * application can now choose to use this tag to specify the desired zoom level. The - * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} can still be used to specify the horizontal or vertical - * crop to achieve aspect ratios different than the native camera sensor.</p> + * <p>Instead of using {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} for zoom, the application can now choose to + * use this tag to specify the desired zoom level.</p> * <p>By using this control, the application gains a simpler way to control zoom, which can * be a combination of optical and digital zoom. For example, a multi-camera system may * contain more than one lens with different focal lengths, and the user can use optical @@ -3506,11 +3504,18 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * respectively.</p> * <p>The camera device may adjust the crop region to account for rounding and other hardware * requirements; the final crop region used will be included in the output capture result.</p> + * <p>The camera sensor output aspect ratio depends on factors such as output stream + * combination and {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}, and shouldn't be adjusted by using + * this control. And the camera device will treat different camera sensor output sizes + * (potentially with in-sensor crop) as the same crop of + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}. As a result, the application shouldn't assume the + * maximum crop region always maps to the same aspect ratio or field of view for the + * sensor output.</p> * <p>Starting from API level 30, it's strongly recommended to use {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} * to take advantage of better support for zoom with logical multi-camera. The benefits * include better precision with optical-digital zoom combination, and ability to do * zoom-out from 1.0x. When using {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for zoom, the crop region in - * the capture request must be either letterboxing or pillarboxing (but not both). The + * the capture request should be left as the default activeArray size. The * coordinate system is post-zoom, meaning that the activeArraySize or * preCorrectionActiveArraySize covers the camera device's field of view "after" zoom. See * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p> @@ -3520,6 +3525,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * capability and mode</p> * <p>This key is available on all devices.</p> * + * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE * @see CaptureRequest#CONTROL_ZOOM_RATIO * @see CaptureRequest#DISTORTION_CORRECTION_MODE * @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 986e6eafac2a..9d4ab0b08e92 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -72,6 +72,7 @@ import android.util.Range; import android.util.Size; import dalvik.annotation.optimization.FastNative; +import dalvik.system.VMRuntime; import java.io.IOException; import java.nio.ByteBuffer; @@ -351,6 +352,7 @@ public class CameraMetadataNative implements Parcelable { if (mMetadataPtr == 0) { throw new OutOfMemoryError("Failed to allocate native CameraMetadata"); } + updateNativeAllocation(); } /** @@ -362,6 +364,7 @@ public class CameraMetadataNative implements Parcelable { if (mMetadataPtr == 0) { throw new OutOfMemoryError("Failed to allocate native CameraMetadata"); } + updateNativeAllocation(); } /** @@ -443,6 +446,7 @@ public class CameraMetadataNative implements Parcelable { public void readFromParcel(Parcel in) { nativeReadFromParcel(in, mMetadataPtr); + updateNativeAllocation(); } /** @@ -533,6 +537,11 @@ public class CameraMetadataNative implements Parcelable { // Delete native pointer, but does not clear it nativeClose(mMetadataPtr); mMetadataPtr = 0; + + if (mBufferSize > 0) { + VMRuntime.getRuntime().registerNativeFree(mBufferSize); + } + mBufferSize = 0; } private <T> T getBase(CameraCharacteristics.Key<T> key) { @@ -1645,9 +1654,26 @@ public class CameraMetadataNative implements Parcelable { return true; } + private void updateNativeAllocation() { + long currentBufferSize = nativeGetBufferSize(mMetadataPtr); + + if (currentBufferSize != mBufferSize) { + if (mBufferSize > 0) { + VMRuntime.getRuntime().registerNativeFree(mBufferSize); + } + + mBufferSize = currentBufferSize; + + if (mBufferSize > 0) { + VMRuntime.getRuntime().registerNativeAllocation(mBufferSize); + } + } + } + private int mCameraId = -1; private boolean mHasMandatoryConcurrentStreams = false; private Size mDisplaySize = new Size(0, 0); + private long mBufferSize = 0; /** * Set the current camera Id. @@ -1705,6 +1731,8 @@ public class CameraMetadataNative implements Parcelable { private static synchronized native boolean nativeIsEmpty(long ptr); @FastNative private static synchronized native int nativeGetEntryCount(long ptr); + @FastNative + private static synchronized native long nativeGetBufferSize(long ptr); @UnsupportedAppUsage @FastNative @@ -1744,6 +1772,8 @@ public class CameraMetadataNative implements Parcelable { mCameraId = other.mCameraId; mHasMandatoryConcurrentStreams = other.mHasMandatoryConcurrentStreams; mDisplaySize = other.mDisplaySize; + updateNativeAllocation(); + other.updateNativeAllocation(); } /** diff --git a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java index 27f8a61b8999..7b6a457411f3 100644 --- a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java +++ b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java @@ -37,12 +37,16 @@ public class FrameNumberTracker { /** the completed frame number for each type of capture results */ private long[] mCompletedFrameNumber = new long[CaptureRequest.REQUEST_TYPE_COUNT]; - /** the skipped frame numbers that don't belong to each type of capture results */ - private final LinkedList<Long>[] mSkippedOtherFrameNumbers = + /** the frame numbers that don't belong to each type of capture results and are yet to be seen + * through an updateTracker() call. Each list holds a list of frame numbers that should appear + * with request types other than that, to which the list corresponds. + */ + private final LinkedList<Long>[] mPendingFrameNumbersWithOtherType = new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT]; - /** the skipped frame numbers that belong to each type of capture results */ - private final LinkedList<Long>[] mSkippedFrameNumbers = + /** the frame numbers that belong to each type of capture results which should appear, but + * haven't yet.*/ + private final LinkedList<Long>[] mPendingFrameNumbers = new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT]; /** frame number -> request type */ @@ -53,8 +57,8 @@ public class FrameNumberTracker { public FrameNumberTracker() { for (int i = 0; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) { mCompletedFrameNumber[i] = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED; - mSkippedOtherFrameNumbers[i] = new LinkedList<Long>(); - mSkippedFrameNumbers[i] = new LinkedList<Long>(); + mPendingFrameNumbersWithOtherType[i] = new LinkedList<Long>(); + mPendingFrameNumbers[i] = new LinkedList<Long>(); } } @@ -66,29 +70,29 @@ public class FrameNumberTracker { int requestType = (int) pair.getValue(); Boolean removeError = false; if (errorFrameNumber == mCompletedFrameNumber[requestType] + 1) { - mCompletedFrameNumber[requestType] = errorFrameNumber; removeError = true; + } + // The error frame number could have also either been in the pending list or one of the + // 'other' pending lists. + if (!mPendingFrameNumbers[requestType].isEmpty()) { + if (errorFrameNumber == mPendingFrameNumbers[requestType].element()) { + mPendingFrameNumbers[requestType].remove(); + removeError = true; + } } else { - if (!mSkippedFrameNumbers[requestType].isEmpty()) { - if (errorFrameNumber == mSkippedFrameNumbers[requestType].element()) { - mCompletedFrameNumber[requestType] = errorFrameNumber; - mSkippedFrameNumbers[requestType].remove(); + for (int i = 1; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) { + int otherType = (requestType + i) % CaptureRequest.REQUEST_TYPE_COUNT; + if (!mPendingFrameNumbersWithOtherType[otherType].isEmpty() && errorFrameNumber + == mPendingFrameNumbersWithOtherType[otherType].element()) { + mPendingFrameNumbersWithOtherType[otherType].remove(); removeError = true; - } - } else { - for (int i = 1; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) { - int otherType = (requestType + i) % CaptureRequest.REQUEST_TYPE_COUNT; - if (!mSkippedOtherFrameNumbers[otherType].isEmpty() && errorFrameNumber - == mSkippedOtherFrameNumbers[otherType].element()) { - mCompletedFrameNumber[requestType] = errorFrameNumber; - mSkippedOtherFrameNumbers[otherType].remove(); - removeError = true; - break; - } + break; } } } if (removeError) { + mCompletedFrameNumber[requestType] = errorFrameNumber; + mPartialResults.remove(errorFrameNumber); iter.remove(); } } @@ -182,7 +186,7 @@ public class FrameNumberTracker { * It validates that all previous frames of the same category have arrived. * * If there is a gap since previous frame number of the same category, assume the frames in - * the gap are other categories and store them in the skipped frame number queue to check + * the gap are other categories and store them in the pending frame number queue to check * against when frames of those categories arrive. */ private void updateCompletedFrameNumber(long frameNumber, @@ -199,25 +203,29 @@ public class FrameNumberTracker { if (frameNumber < maxOtherFrameNumberSeen) { // if frame number is smaller than completed frame numbers of other categories, // it must be: - // - the head of mSkippedFrameNumbers for this category, or - // - in one of other mSkippedOtherFrameNumbers - if (!mSkippedFrameNumbers[requestType].isEmpty()) { - // frame number must be head of current type of mSkippedFrameNumbers if - // mSkippedFrameNumbers isn't empty. - if (frameNumber < mSkippedFrameNumbers[requestType].element()) { + // - the head of mPendingFrameNumbers for this category, or + // - in one of other mPendingFrameNumbersWithOtherType + if (!mPendingFrameNumbers[requestType].isEmpty()) { + // frame number must be head of current type of mPendingFrameNumbers if + // mPendingFrameNumbers isn't empty. + Long pendingFrameNumberSameType = mPendingFrameNumbers[requestType].element(); + if (frameNumber == pendingFrameNumberSameType) { + // frame number matches the head of the pending frame number queue. + // Do this before the inequality checks since this is likely to be the common + // case. + mPendingFrameNumbers[requestType].remove(); + } else if (frameNumber < pendingFrameNumberSameType) { throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); - } else if (frameNumber > mSkippedFrameNumbers[requestType].element()) { + } else { throw new IllegalArgumentException("frame number " + frameNumber + " comes out of order. Expecting " - + mSkippedFrameNumbers[requestType].element()); + + pendingFrameNumberSameType); } - // frame number matches the head of the skipped frame number queue. - mSkippedFrameNumbers[requestType].remove(); } else { - // frame number must be in one of the other mSkippedOtherFrameNumbers. - int index1 = mSkippedOtherFrameNumbers[otherType1].indexOf(frameNumber); - int index2 = mSkippedOtherFrameNumbers[otherType2].indexOf(frameNumber); + // frame number must be in one of the other mPendingFrameNumbersWithOtherType. + int index1 = mPendingFrameNumbersWithOtherType[otherType1].indexOf(frameNumber); + int index2 = mPendingFrameNumbersWithOtherType[otherType2].indexOf(frameNumber); boolean inSkippedOther1 = index1 != -1; boolean inSkippedOther2 = index2 != -1; if (!(inSkippedOther1 ^ inSkippedOther2)) { @@ -225,33 +233,39 @@ public class FrameNumberTracker { + " is a repeat or invalid"); } - // We know the category of frame numbers in skippedOtherFrameNumbers leading up - // to the current frame number. Move them into the correct skippedFrameNumbers. + // We know the category of frame numbers in pendingFrameNumbersWithOtherType leading + // up to the current frame number. The destination is the type which isn't the + // requestType* and isn't the src. Move them into the correct pendingFrameNumbers. + // * : This is since frameNumber is the first frame of requestType that we've + // received in the 'others' list, since for each request type frames come in order. + // All the frames before frameNumber are of the same type. They're not of + // 'requestType', neither of the type of the 'others' list they were found in. The + // remaining option is the 3rd type. LinkedList<Long> srcList, dstList; int index; if (inSkippedOther1) { - srcList = mSkippedOtherFrameNumbers[otherType1]; - dstList = mSkippedFrameNumbers[otherType2]; + srcList = mPendingFrameNumbersWithOtherType[otherType1]; + dstList = mPendingFrameNumbers[otherType2]; index = index1; } else { - srcList = mSkippedOtherFrameNumbers[otherType2]; - dstList = mSkippedFrameNumbers[otherType1]; + srcList = mPendingFrameNumbersWithOtherType[otherType2]; + dstList = mPendingFrameNumbers[otherType1]; index = index2; } for (int i = 0; i < index; i++) { dstList.add(srcList.removeFirst()); } - // Remove current frame number from skippedOtherFrameNumbers + // Remove current frame number from pendingFrameNumbersWithOtherType srcList.remove(); } } else { // there is a gap of unseen frame numbers which should belong to the other - // 2 categories. Put all the skipped frame numbers in the queue. + // 2 categories. Put all the pending frame numbers in the queue. for (long i = Math.max(maxOtherFrameNumberSeen, mCompletedFrameNumber[requestType]) + 1; i < frameNumber; i++) { - mSkippedOtherFrameNumbers[requestType].add(i); + mPendingFrameNumbersWithOtherType[requestType].add(i); } } diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 1b114d3528a2..e65d7b5965d5 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -49,9 +49,6 @@ import com.android.internal.os.SomeArgs; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; /** * A class that coordinates access to the face authentication hardware. @@ -299,6 +296,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * int[], Surface)} with {@code surface} set to null. * * @see FaceManager#enroll(int, byte[], CancellationSignal, EnrollmentCallback, int[], Surface) + * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel, @@ -443,7 +441,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan */ @RequiresPermission(MANAGE_BIOMETRIC) public void generateChallenge(GenerateChallengeCallback callback) { - final List<FaceSensorProperties> faceSensorProperties = getSensorProperties(); + final List<FaceSensorPropertiesInternal> faceSensorProperties = + getSensorPropertiesInternal(); if (faceSensorProperties.isEmpty()) { Slog.e(TAG, "No sensors"); return; @@ -460,7 +459,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan */ @RequiresPermission(MANAGE_BIOMETRIC) public void revokeChallenge() { - final List<FaceSensorProperties> faceSensorProperties = getSensorProperties(); + final List<FaceSensorPropertiesInternal> faceSensorProperties = + getSensorPropertiesInternal(); if (faceSensorProperties.isEmpty()) { Slog.e(TAG, "No sensors during revokeChallenge"); } @@ -597,6 +597,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * Determine if there is a face enrolled. * * @return true if a face is enrolled, false otherwise + * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public boolean hasEnrolledTemplates() { @@ -632,6 +633,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * Determine if face authentication sensor hardware is present and functional. * * @return true if hardware is present and functional, false otherwise. + * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public boolean isHardwareDetected() { @@ -648,17 +650,32 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** + * Retrieves a list of properties for all face authentication sensors on the device. + * @hide + */ + @NonNull + public List<FaceSensorProperties> getSensorProperties() { + final List<FaceSensorProperties> properties = new ArrayList<>(); + final List<FaceSensorPropertiesInternal> internalProperties + = getSensorPropertiesInternal(); + for (FaceSensorPropertiesInternal internalProp : internalProperties) { + properties.add(FaceSensorProperties.from(internalProp)); + } + return properties; + } + + /** * Get statically configured sensor properties. * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) @NonNull - public List<FaceSensorProperties> getSensorProperties() { + public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal() { try { if (mService == null || !mService.isHardwareDetected(mContext.getOpPackageName())) { return new ArrayList<>(); } - return mService.getSensorProperties(mContext.getOpPackageName()); + return mService.getSensorPropertiesInternal(mContext.getOpPackageName()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -874,6 +891,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan /** * Container for callback data from {@link FaceManager#authenticate(CryptoObject, * CancellationSignal, int, AuthenticationCallback, Handler)}. + * @hide */ public static class AuthenticationResult { private Face mFace; @@ -943,6 +961,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * FaceManager#authenticate(CryptoObject, CancellationSignal, * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening * to face events. + * @hide */ public abstract static class AuthenticationCallback extends BiometricAuthenticator.AuthenticationCallback { diff --git a/core/java/android/hardware/face/FaceSensorProperties.java b/core/java/android/hardware/face/FaceSensorProperties.java index 23ad9353a206..e61d93166cc5 100644 --- a/core/java/android/hardware/face/FaceSensorProperties.java +++ b/core/java/android/hardware/face/FaceSensorProperties.java @@ -16,76 +16,25 @@ package android.hardware.face; -import android.os.Parcel; -import android.os.Parcelable; +import android.hardware.biometrics.SensorProperties; /** * Container for face sensor properties. * @hide */ -public class FaceSensorProperties implements Parcelable { +public class FaceSensorProperties extends SensorProperties { /** - * A statically configured ID representing this sensor. Sensor IDs must be unique across all - * biometrics across the device, starting at 0, and in increments of 1. + * @hide */ - public final int sensorId; - /** - * True if the sensor is able to perform generic face detection, without running the - * matching algorithm, and without affecting the lockout counter. - */ - public final boolean supportsFaceDetection; - /** - * True if the sensor is able to provide self illumination in dark scenarios, without support - * from above the HAL. - */ - public final boolean supportsSelfIllumination; - /** - * Maximum number of enrollments a user/profile can have. - */ - public final int maxTemplatesAllowed; - + public static FaceSensorProperties from(FaceSensorPropertiesInternal internalProp) { + return new FaceSensorProperties(internalProp.sensorId, internalProp.sensorStrength); + } /** - * Initializes SensorProperties with specified values + * @hide */ - public FaceSensorProperties(int sensorId, boolean supportsFaceDetection, - boolean supportsSelfIllumination, int maxTemplatesAllowed) { - this.sensorId = sensorId; - this.supportsFaceDetection = supportsFaceDetection; - this.supportsSelfIllumination = supportsSelfIllumination; - this.maxTemplatesAllowed = maxTemplatesAllowed; + public FaceSensorProperties(int sensorId, int sensorStrength) { + super(sensorId, sensorStrength); } - protected FaceSensorProperties(Parcel in) { - sensorId = in.readInt(); - supportsFaceDetection = in.readBoolean(); - supportsSelfIllumination = in.readBoolean(); - maxTemplatesAllowed = in.readInt(); - } - - public static final Creator<FaceSensorProperties> CREATOR = - new Creator<FaceSensorProperties>() { - @Override - public FaceSensorProperties createFromParcel(Parcel in) { - return new FaceSensorProperties(in); - } - - @Override - public FaceSensorProperties[] newArray(int size) { - return new FaceSensorProperties[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(sensorId); - dest.writeBoolean(supportsFaceDetection); - dest.writeBoolean(supportsSelfIllumination); - dest.writeInt(maxTemplatesAllowed); - } } diff --git a/core/java/android/hardware/face/FaceSensorProperties.aidl b/core/java/android/hardware/face/FaceSensorPropertiesInternal.aidl index d83ee4b17fa7..75bc250f52e2 100644 --- a/core/java/android/hardware/face/FaceSensorProperties.aidl +++ b/core/java/android/hardware/face/FaceSensorPropertiesInternal.aidl @@ -15,4 +15,4 @@ */ package android.hardware.face; -parcelable FaceSensorProperties;
\ No newline at end of file +parcelable FaceSensorPropertiesInternal;
\ No newline at end of file diff --git a/core/java/android/hardware/face/FaceSensorPropertiesInternal.java b/core/java/android/hardware/face/FaceSensorPropertiesInternal.java new file mode 100644 index 000000000000..e91554b532b0 --- /dev/null +++ b/core/java/android/hardware/face/FaceSensorPropertiesInternal.java @@ -0,0 +1,81 @@ +/* + * 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.hardware.face; + +import android.hardware.biometrics.SensorProperties; +import android.hardware.biometrics.SensorPropertiesInternal; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Container for face sensor properties. + * @hide + */ +public class FaceSensorPropertiesInternal extends SensorPropertiesInternal { + /** + * True if the sensor is able to perform generic face detection, without running the + * matching algorithm, and without affecting the lockout counter. + */ + public final boolean supportsFaceDetection; + /** + * True if the sensor is able to provide self illumination in dark scenarios, without support + * from above the HAL. + */ + public final boolean supportsSelfIllumination; + + /** + * Initializes SensorProperties with specified values + */ + public FaceSensorPropertiesInternal(int sensorId, @SensorProperties.Strength int strength, + int maxEnrollmentsPerUser, boolean supportsFaceDetection, + boolean supportsSelfIllumination) { + super(sensorId, strength, maxEnrollmentsPerUser); + this.supportsFaceDetection = supportsFaceDetection; + this.supportsSelfIllumination = supportsSelfIllumination; + } + + protected FaceSensorPropertiesInternal(Parcel in) { + super(in); + supportsFaceDetection = in.readBoolean(); + supportsSelfIllumination = in.readBoolean(); + } + + public static final Creator<FaceSensorPropertiesInternal> CREATOR = + new Creator<FaceSensorPropertiesInternal>() { + @Override + public FaceSensorPropertiesInternal createFromParcel(Parcel in) { + return new FaceSensorPropertiesInternal(in); + } + + @Override + public FaceSensorPropertiesInternal[] newArray(int size) { + return new FaceSensorPropertiesInternal[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeBoolean(supportsFaceDetection); + dest.writeBoolean(supportsSelfIllumination); + } +} diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index e744c840c298..b85b6f7d8174 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -19,7 +19,7 @@ import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.face.IFaceServiceReceiver; import android.hardware.face.Face; -import android.hardware.face.FaceSensorProperties; +import android.hardware.face.FaceSensorPropertiesInternal; import android.view.Surface; /** @@ -29,7 +29,7 @@ import android.view.Surface; */ interface IFaceService { // Retrieve static sensor properties for all face sensors - List<FaceSensorProperties> getSensorProperties(String opPackageName); + List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName); // Authenticate the given sessionId with a face void authenticate(IBinder token, long operationId, int userid, IFaceServiceReceiver receiver, diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index c12bb39c3175..23de303efae6 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -53,10 +53,7 @@ import android.view.Surface; import java.security.Signature; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import javax.crypto.Cipher; import javax.crypto.Mac; @@ -608,7 +605,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing */ @RequiresPermission(MANAGE_FINGERPRINT) public void generateChallenge(GenerateChallengeCallback callback) { - final List<FingerprintSensorProperties> fingerprintSensorProperties = getSensorProperties(); + final List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties = + getSensorPropertiesInternal(); if (fingerprintSensorProperties.isEmpty()) { Slog.e(TAG, "No sensors"); return; @@ -746,7 +744,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public void setUdfpsOverlayController(IUdfpsOverlayController controller) { + public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) { if (mService == null) { Slog.w(TAG, "setUdfpsOverlayController: no fingerprint service"); return; @@ -763,14 +761,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public void onFingerDown(int x, int y, float minor, float major) { + public void onFingerDown(int sensorId, int x, int y, float minor, float major) { if (mService == null) { Slog.w(TAG, "onFingerDown: no fingerprint service"); return; } try { - mService.onFingerDown(x, y, minor, major); + mService.onFingerDown(sensorId, x, y, minor, major); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -780,14 +778,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public void onFingerUp() { + public void onFingerUp(int sensorId) { if (mService == null) { Slog.w(TAG, "onFingerDown: no fingerprint service"); return; } try { - mService.onFingerUp(); + mService.onFingerUp(sensorId); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -850,17 +848,32 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } /** + * Retrieves a list of properties for all fingerprint sensors on the device. + * @hide + */ + @NonNull + public List<FingerprintSensorProperties> getSensorProperties() { + final List<FingerprintSensorProperties> properties = new ArrayList<>(); + final List<FingerprintSensorPropertiesInternal> internalProperties + = getSensorPropertiesInternal(); + for (FingerprintSensorPropertiesInternal internalProp : internalProperties) { + properties.add(FingerprintSensorProperties.from(internalProp)); + } + return properties; + } + + /** * Get statically configured sensor properties. * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) @NonNull - public List<FingerprintSensorProperties> getSensorProperties() { + public List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal() { try { if (mService == null || !mService.isHardwareDetected(mContext.getOpPackageName())) { return new ArrayList<>(); } - return mService.getSensorProperties(mContext.getOpPackageName()); + return mService.getSensorPropertiesInternal(mContext.getOpPackageName()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java b/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java index 718141a4845a..684d7d9cf0a0 100644 --- a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java +++ b/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java @@ -18,9 +18,6 @@ package android.hardware.fingerprint; import android.annotation.IntDef; import android.hardware.biometrics.SensorProperties; -import android.hardware.face.FaceSensorProperties; -import android.os.Parcel; -import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -30,14 +27,39 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public class FingerprintSensorProperties extends SensorProperties { - + /** + * @hide + */ public static final int TYPE_UNKNOWN = 0; + + /** + * @hide + */ public static final int TYPE_REAR = 1; + + /** + * @hide + */ public static final int TYPE_UDFPS_ULTRASONIC = 2; + + /** + * @hide + */ public static final int TYPE_UDFPS_OPTICAL = 3; + + /** + * @hide + */ public static final int TYPE_POWER_BUTTON = 4; + + /** + * @hide + */ public static final int TYPE_HOME_BUTTON = 5; + /** + * @hide + */ @IntDef({TYPE_UNKNOWN, TYPE_REAR, TYPE_UDFPS_ULTRASONIC, @@ -47,60 +69,34 @@ public class FingerprintSensorProperties extends SensorProperties { @Retention(RetentionPolicy.SOURCE) public @interface SensorType {} - public final @SensorType int sensorType; - // IBiometricsFingerprint@2.1 does not manage timeout below the HAL, so the Gatekeeper HAT - // cannot be checked - public final boolean resetLockoutRequiresHardwareAuthToken; + @SensorType final int mSensorType; /** - * Initializes SensorProperties with specified values + * Constructs a {@link FingerprintSensorProperties} from the internal parcelable representation. + * @hide */ - public FingerprintSensorProperties(int sensorId, @Strength int strength, - int maxEnrollmentsPerUser, @SensorType int sensorType, - boolean resetLockoutRequiresHardwareAuthToken) { - super(sensorId, strength, maxEnrollmentsPerUser); - this.sensorType = sensorType; - this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken; - } - - protected FingerprintSensorProperties(Parcel in) { - super(in); - sensorType = in.readInt(); - resetLockoutRequiresHardwareAuthToken = in.readBoolean(); + public static FingerprintSensorProperties from( + FingerprintSensorPropertiesInternal internalProp) { + return new FingerprintSensorProperties(internalProp.sensorId, + internalProp.sensorStrength, + internalProp.sensorType); } - public static final Creator<FingerprintSensorProperties> CREATOR = - new Creator<FingerprintSensorProperties>() { - @Override - public FingerprintSensorProperties createFromParcel(Parcel in) { - return new FingerprintSensorProperties(in); - } - - @Override - public FingerprintSensorProperties[] newArray(int size) { - return new FingerprintSensorProperties[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(sensorType); - dest.writeBoolean(resetLockoutRequiresHardwareAuthToken); + /** + * @hide + */ + public FingerprintSensorProperties(int sensorId, int sensorStrength, + @SensorType int sensorType) { + super(sensorId, sensorStrength); + mSensorType = sensorType; } - public boolean isAnyUdfpsType() { - switch (sensorType) { - case TYPE_UDFPS_OPTICAL: - case TYPE_UDFPS_ULTRASONIC: - return true; - default: - return false; - } + /** + * @hide + * @return The sensor's type. + */ + @SensorType + public int getSensorType() { + return mSensorType; } } diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.aidl b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.aidl index 83d853dd4048..51705ef89ef3 100644 --- a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.aidl +++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.aidl @@ -15,4 +15,4 @@ */ package android.hardware.fingerprint; -parcelable FingerprintSensorProperties;
\ No newline at end of file +parcelable FingerprintSensorPropertiesInternal;
\ No newline at end of file diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java new file mode 100644 index 000000000000..d5ce9e3890b5 --- /dev/null +++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.fingerprint; + +import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL; +import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC; + +import android.hardware.biometrics.SensorProperties; +import android.hardware.biometrics.SensorPropertiesInternal; +import android.os.Parcel; + +/** + * Container for fingerprint sensor properties. + * @hide + */ +public class FingerprintSensorPropertiesInternal extends SensorPropertiesInternal { + public final @FingerprintSensorProperties.SensorType int sensorType; + // IBiometricsFingerprint@2.1 does not manage timeout below the HAL, so the Gatekeeper HAT + // cannot be checked + public final boolean resetLockoutRequiresHardwareAuthToken; + + /** + * Initializes SensorProperties with specified values + */ + public FingerprintSensorPropertiesInternal(int sensorId, + @SensorProperties.Strength int strength, int maxEnrollmentsPerUser, + @FingerprintSensorProperties.SensorType int sensorType, + boolean resetLockoutRequiresHardwareAuthToken) { + super(sensorId, strength, maxEnrollmentsPerUser); + this.sensorType = sensorType; + this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken; + } + + protected FingerprintSensorPropertiesInternal(Parcel in) { + super(in); + sensorType = in.readInt(); + resetLockoutRequiresHardwareAuthToken = in.readBoolean(); + } + + public static final Creator<FingerprintSensorPropertiesInternal> CREATOR = + new Creator<FingerprintSensorPropertiesInternal>() { + @Override + public FingerprintSensorPropertiesInternal createFromParcel(Parcel in) { + return new FingerprintSensorPropertiesInternal(in); + } + + @Override + public FingerprintSensorPropertiesInternal[] newArray(int size) { + return new FingerprintSensorPropertiesInternal[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(sensorType); + dest.writeBoolean(resetLockoutRequiresHardwareAuthToken); + } + + public boolean isAnyUdfpsType() { + switch (sensorType) { + case TYPE_UDFPS_OPTICAL: + case TYPE_UDFPS_ULTRASONIC: + return true; + default: + return false; + } + } +} diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index cc2b520b3152..7af73800d64e 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -21,7 +21,7 @@ import android.hardware.fingerprint.IFingerprintClientActiveCallback; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.hardware.fingerprint.IUdfpsOverlayController; import android.hardware.fingerprint.Fingerprint; -import android.hardware.fingerprint.FingerprintSensorProperties; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.view.Surface; import java.util.List; @@ -31,7 +31,7 @@ import java.util.List; */ interface IFingerprintService { // Retrieve static sensor properties for all fingerprint sensors - List<FingerprintSensorProperties> getSensorProperties(String opPackageName); + List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName); // Authenticate the given sessionId with a fingerprint. This is protected by // USE_FINGERPRINT/USE_BIOMETRIC permission. This is effectively deprecated, since it only comes @@ -121,10 +121,10 @@ interface IFingerprintService { void initializeConfiguration(int sensorId, int strength); // Notifies about a finger touching the sensor area. - void onFingerDown(int x, int y, float minor, float major); + void onFingerDown(int sensorId, int x, int y, float minor, float major); // Notifies about a finger leaving the sensor area. - void onFingerUp(); + void onFingerUp(int sensorId); // Sets the controller for managing the UDFPS overlay. void setUdfpsOverlayController(in IUdfpsOverlayController controller); diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl index a57726c4afe4..58b7046ad991 100644 --- a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl +++ b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl @@ -21,11 +21,11 @@ package android.hardware.fingerprint; */ oneway interface IUdfpsOverlayController { // Shows the overlay. - void showUdfpsOverlay(); + void showUdfpsOverlay(int sensorId); // Hides the overlay. - void hideUdfpsOverlay(); + void hideUdfpsOverlay(int sensorId); // Shows debug messages on the UDFPS overlay. - void setDebugMessage(String message); + void setDebugMessage(int sensorId, String message); } diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java index f6cd726dda4a..1173c311bd26 100644 --- a/core/java/android/hardware/input/InputManagerInternal.java +++ b/core/java/android/hardware/input/InputManagerInternal.java @@ -78,4 +78,26 @@ public abstract class InputManagerInternal { */ public abstract boolean transferTouchFocus(@NonNull IBinder fromChannelToken, @NonNull IBinder toChannelToken); + + /** Registers the {@link LidSwitchCallback} to begin receiving notifications. */ + public abstract void registerLidSwitchCallback(@NonNull LidSwitchCallback callbacks); + + /** + * Unregisters a {@link LidSwitchCallback callback} previously registered with + * {@link #registerLidSwitchCallback(LidSwitchCallback)}. + */ + public abstract void unregisterLidSwitchCallback(@NonNull LidSwitchCallback callbacks); + + /** Callback interface for notifications relating to the lid switch. */ + public interface LidSwitchCallback { + /** + * This callback is invoked when the lid switch changes state. Will be triggered once on + * registration of the callback with a {@code whenNanos} of 0 and then on every subsequent + * change in lid switch state. + * + * @param whenNanos the time when the change occurred + * @param lidOpen true if the lid is open + */ + void notifyLidSwitchChanged(long whenNanos, boolean lidOpen); + } } diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 651494d1c99c..cad103e9f082 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -21,13 +21,14 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; -import android.net.util.LinkPropertiesUtils; -import android.net.util.LinkPropertiesUtils.CompareResult; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import com.android.net.module.util.LinkPropertiesUtils; +import com.android.net.module.util.LinkPropertiesUtils.CompareResult; + import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java index 0eb3c1e8ad01..51c5a50dcafb 100644 --- a/core/java/android/net/MacAddress.java +++ b/core/java/android/net/MacAddress.java @@ -20,12 +20,12 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; -import android.net.util.MacAddressUtils; import android.net.wifi.WifiInfo; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.Preconditions; +import com.android.net.module.util.MacAddressUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index 98760761736d..a8b45e9d128b 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -22,11 +22,12 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; -import android.net.util.NetUtils; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import com.android.net.module.util.NetUtils; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.Inet4Address; diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index fdbf79a9f5d2..d889b155cf64 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -21,6 +21,7 @@ import static android.os.BatteryStatsManager.NUM_WIFI_STATES; import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES; import android.annotation.IntDef; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.job.JobParameters; import android.compat.annotation.UnsupportedAppUsage; @@ -2889,14 +2890,17 @@ public abstract class BatteryStats implements Parcelable { /** * Returns the approximate CPU time (in microseconds) spent by the system server handling - * incoming service calls from apps. + * incoming service calls from apps. The result is returned as an array of longs, + * organized as a sequence like this: + * <pre> + * cluster1-speeed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ... + * </pre> * - * @param cluster the index of the CPU cluster. - * @param step the index of the CPU speed. This is not the actual speed of the CPU. * @see com.android.internal.os.PowerProfile#getNumCpuClusters() * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int) */ - public abstract long getSystemServiceTimeAtCpuSpeed(int cluster, int step); + @Nullable + public abstract long[] getSystemServiceTimeAtCpuSpeeds(); /** * Returns the total, last, or current battery uptime in microseconds. diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index df1f1b21eba3..6ba1627dde47 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -29,6 +29,7 @@ import android.content.pm.ResolveInfo; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.provider.Settings; +import android.text.TextUtils; import android.util.Log; import android.widget.Toast; @@ -43,11 +44,17 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; -/** @hide */ +/** + * GraphicsEnvironment sets up necessary properties for the graphics environment of the + * application process. + * GraphicsEnvironment uses a bunch of settings global variables to determine the setup, + * the change of settings global variables will only take effect before setup() is called, + * and any subsequent change will not impact the current running processes. + * + * @hide + */ public class GraphicsEnvironment { private static final GraphicsEnvironment sInstance = new GraphicsEnvironment(); @@ -64,27 +71,35 @@ public class GraphicsEnvironment { private static final String SYSTEM_DRIVER_NAME = "system"; private static final String SYSTEM_DRIVER_VERSION_NAME = ""; private static final long SYSTEM_DRIVER_VERSION_CODE = 0; + + // System properties related to updatable graphics drivers. private static final String PROPERTY_GFX_DRIVER_PRODUCTION = "ro.gfx.driver.0"; private static final String PROPERTY_GFX_DRIVER_PRERELEASE = "ro.gfx.driver.1"; private static final String PROPERTY_GFX_DRIVER_BUILD_TIME = "ro.gfx.driver_build_time"; + + // Metadata flags within the <application> tag in the AndroidManifest.xml file. private static final String METADATA_DRIVER_BUILD_TIME = "com.android.graphics.driver.build_time"; private static final String METADATA_DEVELOPER_DRIVER_ENABLE = "com.android.graphics.developerdriver.enable"; private static final String METADATA_INJECT_LAYERS_ENABLE = "com.android.graphics.injectLayers.enable"; + + private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*"; + private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt"; + + // ANGLE related properties. private static final String ANGLE_RULES_FILE = "a4a_rules.json"; private static final String ANGLE_TEMP_RULES = "debug.angle.rules"; private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID"; private static final String ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE = "android.app.action.ANGLE_FOR_ANDROID_TOAST_MESSAGE"; private static final String INTENT_KEY_A4A_TOAST_MESSAGE = "A4A Toast Message"; - private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*"; - private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt"; + private static final int VULKAN_1_0 = 0x00400000; private static final int VULKAN_1_1 = 0x00401000; - // UPDATABLE_DRIVER_ALL_APPS + // Values for UPDATABLE_DRIVER_ALL_APPS // 0: Default (Invalid values fallback to default as well) // 1: All apps use updatable production driver // 2: All apps use updatable prerelease driver @@ -94,10 +109,21 @@ public class GraphicsEnvironment { private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2; private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF = 3; + // Values for GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE + private static final int ANGLE_GL_DRIVER_ALL_ANGLE_ON = 1; + private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0; + + // Values for GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES + private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default"; + private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle"; + private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native"; + private ClassLoader mClassLoader; private String mLibrarySearchPaths; private String mLibraryPermittedPaths; + private int mAngleOptInIndex = -1; + /** * Set up GraphicsEnvironment */ @@ -106,12 +132,15 @@ public class GraphicsEnvironment { final String packageName = context.getPackageName(); final ApplicationInfo appInfoWithMetaData = getAppInfoWithMetadata(context, pm, packageName); + Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers"); setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData); Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); + Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle"); setupAngle(context, coreSettings, pm, packageName); Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); + Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver"); if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) { setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME, SYSTEM_DRIVER_VERSION_CODE, @@ -122,49 +151,37 @@ public class GraphicsEnvironment { } /** - * Hint for GraphicsEnvironment that an activity is launching on the process. - * Then the app process is allowed to send stats to GpuStats module. - */ - public static native void hintActivityLaunch(); - - /** * Query to determine if ANGLE should be used */ - public static boolean shouldUseAngle(Context context, Bundle coreSettings, + private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) { - if (packageName.isEmpty()) { - Log.v(TAG, "No package name available yet, ANGLE should not be used"); + if (TextUtils.isEmpty(packageName)) { + Log.v(TAG, "No package name specified, ANGLE should not be used"); return false; } - final String devOptIn = getDriverForPkg(context, coreSettings, packageName); - if (DEBUG) { - Log.v(TAG, "ANGLE Developer option for '" + packageName + "' " - + "set to: '" + devOptIn + "'"); - } + final String devOptIn = getDriverForPackage(context, coreSettings, packageName); + Log.v(TAG, "ANGLE Developer option for '" + packageName + "' " + + "set to: '" + devOptIn + "'"); - // We only want to use ANGLE if the app is allowlisted or the developer has + // We only want to use ANGLE if the app is in the allowlist, or the developer has // explicitly chosen something other than default driver. // The allowlist will be generated by the ANGLE APK at both boot time and // ANGLE update time. It will only include apps mentioned in the rules file. - final boolean allowlisted = checkAngleAllowlist(context, coreSettings, packageName); - final boolean requested = devOptIn.equals(sDriverMap.get(OpenGlDriverChoice.ANGLE)); - final boolean useAngle = (allowlisted || requested); - if (!useAngle) { - return false; - } + final boolean allowed = checkAngleAllowlist(context, coreSettings, packageName); + final boolean requested = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE); - if (allowlisted) { + if (allowed) { Log.v(TAG, "ANGLE allowlist includes " + packageName); } if (requested) { Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn); } - return true; + return allowed || requested; } - private static int getVulkanVersion(PackageManager pm) { + private int getVulkanVersion(PackageManager pm) { // PackageManager doesn't have an API to retrieve the version of a specific feature, and we // need to avoid retrieving all system features here and looping through them. if (pm.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, VULKAN_1_1)) { @@ -181,7 +198,7 @@ public class GraphicsEnvironment { /** * Check whether application is has set the manifest metadata for layer injection. */ - private static boolean canInjectLayers(ApplicationInfo ai) { + private boolean canInjectLayers(ApplicationInfo ai) { return (ai.metaData != null && ai.metaData.getBoolean(METADATA_INJECT_LAYERS_ENABLE) && setInjectLayersPrSetDumpable()); } @@ -239,7 +256,7 @@ public class GraphicsEnvironment { /** * Return the debug layer app's on-disk and in-APK lib directories */ - private static String getDebugLayerAppPaths(IPackageManager pm, String packageName) { + private String getDebugLayerAppPaths(IPackageManager pm, String packageName) { final ApplicationInfo appInfo; try { appInfo = pm.getApplicationInfo(packageName, PackageManager.MATCH_ALL, @@ -249,6 +266,7 @@ public class GraphicsEnvironment { } if (appInfo == null) { Log.w(TAG, "Debug layer app '" + packageName + "' not installed"); + return ""; } final String abi = chooseAbi(appInfo); @@ -315,23 +333,6 @@ public class GraphicsEnvironment { setLayerPaths(mClassLoader, layerPaths); } - enum OpenGlDriverChoice { - DEFAULT, - NATIVE, - ANGLE - } - - private static final Map<OpenGlDriverChoice, String> sDriverMap = buildMap(); - private static Map<OpenGlDriverChoice, String> buildMap() { - final Map<OpenGlDriverChoice, String> map = new HashMap<>(); - map.put(OpenGlDriverChoice.DEFAULT, "default"); - map.put(OpenGlDriverChoice.ANGLE, "angle"); - map.put(OpenGlDriverChoice.NATIVE, "native"); - - return map; - } - - private static List<String> getGlobalSettingsString(ContentResolver contentResolver, Bundle bundle, String globalSetting) { @@ -353,11 +354,10 @@ public class GraphicsEnvironment { return valueList; } - private static int getGlobalSettingsPkgIndex(String pkgName, - List<String> globalSettingsDriverPkgs) { - for (int pkgIndex = 0; pkgIndex < globalSettingsDriverPkgs.size(); pkgIndex++) { - if (globalSettingsDriverPkgs.get(pkgIndex).equals(pkgName)) { - return pkgIndex; + private static int getPackageIndex(String packageName, List<String> packages) { + for (int idx = 0; idx < packages.size(); idx++) { + if (packages.get(idx).equals(packageName)) { + return idx; } } @@ -378,50 +378,54 @@ public class GraphicsEnvironment { return ai; } - private static String getDriverForPkg(Context context, Bundle bundle, String packageName) { - final String allUseAngle; + private String getDriverForPackage(Context context, Bundle bundle, String packageName) { + final int allUseAngle; if (bundle != null) { allUseAngle = - bundle.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); + bundle.getInt(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); } else { ContentResolver contentResolver = context.getContentResolver(); - allUseAngle = Settings.Global.getString(contentResolver, - Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); + allUseAngle = Settings.Global.getInt(contentResolver, + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE, + ANGLE_GL_DRIVER_ALL_ANGLE_OFF); } - if ((allUseAngle != null) && allUseAngle.equals("1")) { - return sDriverMap.get(OpenGlDriverChoice.ANGLE); + if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) { + Log.v(TAG, "Turn on ANGLE for all applications."); + return ANGLE_GL_DRIVER_CHOICE_ANGLE; + } + + // Make sure we have a good package name + if (TextUtils.isEmpty(packageName)) { + return ANGLE_GL_DRIVER_CHOICE_DEFAULT; } final ContentResolver contentResolver = context.getContentResolver(); - final List<String> globalSettingsDriverPkgs = + final List<String> optInPackages = getGlobalSettingsString(contentResolver, bundle, Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS); - final List<String> globalSettingsDriverValues = + final List<String> optInValues = getGlobalSettingsString(contentResolver, bundle, Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES); - // Make sure we have a good package name - if ((packageName == null) || (packageName.isEmpty())) { - return sDriverMap.get(OpenGlDriverChoice.DEFAULT); - } // Make sure we have good settings to use - if (globalSettingsDriverPkgs.size() != globalSettingsDriverValues.size()) { + if (optInPackages.size() != optInValues.size()) { Log.w(TAG, "Global.Settings values are invalid: " - + "globalSettingsDriverPkgs.size = " - + globalSettingsDriverPkgs.size() + ", " - + "globalSettingsDriverValues.size = " - + globalSettingsDriverValues.size()); - return sDriverMap.get(OpenGlDriverChoice.DEFAULT); + + "number of packages: " + + optInPackages.size() + ", " + + "number of values: " + + optInValues.size()); + return ANGLE_GL_DRIVER_CHOICE_DEFAULT; } - final int pkgIndex = getGlobalSettingsPkgIndex(packageName, globalSettingsDriverPkgs); + final int pkgIndex = getPackageIndex(packageName, optInPackages); if (pkgIndex < 0) { - return sDriverMap.get(OpenGlDriverChoice.DEFAULT); + return ANGLE_GL_DRIVER_CHOICE_DEFAULT; } + mAngleOptInIndex = pkgIndex; - return globalSettingsDriverValues.get(pkgIndex); + return optInValues.get(pkgIndex); } /** @@ -446,27 +450,28 @@ public class GraphicsEnvironment { } /** - * Check for ANGLE debug package, but only for apps that can load them (dumpable) + * Check for ANGLE debug package, but only for apps that can load them. + * An application can load ANGLE debug package if it is a debuggable application, or + * the device is debuggable. */ private String getAngleDebugPackage(Context context, Bundle coreSettings) { - if (isDebuggable()) { - String debugPackage; - - if (coreSettings != null) { - debugPackage = - coreSettings.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE); - } else { - ContentResolver contentResolver = context.getContentResolver(); - debugPackage = Settings.Global.getString(contentResolver, - Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE); - } - - if ((debugPackage != null) && (!debugPackage.isEmpty())) { - return debugPackage; - } + if (!isDebuggable()) { + return ""; } + final String debugPackage; - return ""; + if (coreSettings != null) { + debugPackage = + coreSettings.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE); + } else { + ContentResolver contentResolver = context.getContentResolver(); + debugPackage = Settings.Global.getString(contentResolver, + Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE); + } + if (TextUtils.isEmpty(debugPackage)) { + return ""; + } + return debugPackage; } /** @@ -474,7 +479,7 @@ public class GraphicsEnvironment { * True: Temporary rules file was loaded. * False: Temporary rules file was *not* loaded. */ - private static boolean setupAngleWithTempRulesFile(Context context, + private boolean setupAngleWithTempRulesFile(Context context, String packageName, String paths, String devOptIn) { @@ -491,7 +496,7 @@ public class GraphicsEnvironment { final String angleTempRules = SystemProperties.get(ANGLE_TEMP_RULES); - if ((angleTempRules == null) || angleTempRules.isEmpty()) { + if (TextUtils.isEmpty(angleTempRules)) { Log.v(TAG, "System property '" + ANGLE_TEMP_RULES + "' is not set or is empty"); return false; } @@ -510,7 +515,8 @@ public class GraphicsEnvironment { final long rulesLength = stream.getChannel().size(); Log.i(TAG, "Loaded temporary ANGLE rules from " + angleTempRules); - setAngleInfo(paths, packageName, devOptIn, rulesFd, rulesOffset, rulesLength); + setAngleInfo(paths, packageName, devOptIn, null, + rulesFd, rulesOffset, rulesLength); stream.close(); @@ -534,12 +540,13 @@ public class GraphicsEnvironment { * True: APK rules file was loaded. * False: APK rules file was *not* loaded. */ - private static boolean setupAngleRulesApk(String anglePkgName, + private boolean setupAngleRulesApk(String anglePkgName, ApplicationInfo angleInfo, PackageManager pm, String packageName, String paths, - String devOptIn) { + String devOptIn, + String[] features) { // Pass the rules file to loader for ANGLE decisions try { final AssetManager angleAssets = pm.getResourcesForApplication(angleInfo).getAssets(); @@ -547,7 +554,7 @@ public class GraphicsEnvironment { try { final AssetFileDescriptor assetsFd = angleAssets.openFd(ANGLE_RULES_FILE); - setAngleInfo(paths, packageName, devOptIn, assetsFd.getFileDescriptor(), + setAngleInfo(paths, packageName, devOptIn, features, assetsFd.getFileDescriptor(), assetsFd.getStartOffset(), assetsFd.getLength()); assetsFd.close(); @@ -567,7 +574,7 @@ public class GraphicsEnvironment { /** * Pull ANGLE allowlist from GlobalSettings and compare against current package */ - private static boolean checkAngleAllowlist(Context context, Bundle bundle, String packageName) { + private boolean checkAngleAllowlist(Context context, Bundle bundle, String packageName) { final ContentResolver contentResolver = context.getContentResolver(); final List<String> angleAllowlist = getGlobalSettingsString(contentResolver, bundle, @@ -583,11 +590,12 @@ public class GraphicsEnvironment { * * @param context * @param bundle - * @param packageName + * @param pm + * @param packageName - package name of the application. * @return true: ANGLE setup successfully * false: ANGLE not setup (not on allowlist, ANGLE not present, etc.) */ - public boolean setupAngle(Context context, Bundle bundle, PackageManager pm, + private boolean setupAngle(Context context, Bundle bundle, PackageManager pm, String packageName) { if (!shouldUseAngle(context, bundle, packageName)) { @@ -612,18 +620,18 @@ public class GraphicsEnvironment { // Otherwise, check to see if ANGLE is properly installed if (angleInfo == null) { anglePkgName = getAnglePackageName(pm); - if (!anglePkgName.isEmpty()) { - Log.i(TAG, "ANGLE package enabled: " + anglePkgName); - try { - // Production ANGLE libraries must be pre-installed as a system app - angleInfo = pm.getApplicationInfo(anglePkgName, - PackageManager.MATCH_SYSTEM_ONLY); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed"); - return false; - } - } else { - Log.e(TAG, "Failed to find ANGLE package."); + if (TextUtils.isEmpty(anglePkgName)) { + Log.w(TAG, "Failed to find ANGLE package."); + return false; + } + + Log.i(TAG, "ANGLE package enabled: " + anglePkgName); + try { + // Production ANGLE libraries must be pre-installed as a system app + angleInfo = pm.getApplicationInfo(anglePkgName, + PackageManager.MATCH_SYSTEM_ONLY); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed"); return false; } } @@ -645,15 +653,18 @@ public class GraphicsEnvironment { // load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before // and can confidently answer yes/no based on the previously set developer // option value. - final String devOptIn = getDriverForPkg(context, bundle, packageName); + final String devOptIn = getDriverForPackage(context, bundle, packageName); if (setupAngleWithTempRulesFile(context, packageName, paths, devOptIn)) { // We setup ANGLE with a temp rules file, so we're done here. return true; } - if (setupAngleRulesApk(anglePkgName, angleInfo, pm, packageName, paths, devOptIn)) { - // We setup ANGLE with rules from the APK, so we're done here. + String[] features = getAngleEglFeatures(context, bundle); + + if (setupAngleRulesApk( + anglePkgName, angleInfo, pm, packageName, paths, devOptIn, features)) { + // ANGLE with rules is set up from the APK, hence return. return true; } @@ -719,10 +730,23 @@ public class GraphicsEnvironment { } } + private String[] getAngleEglFeatures(Context context, Bundle coreSettings) { + if (mAngleOptInIndex < 0) { + return null; + } + + final List<String> featuresLists = getGlobalSettingsString( + context.getContentResolver(), coreSettings, Settings.Global.ANGLE_EGL_FEATURES); + if (featuresLists.size() <= mAngleOptInIndex) { + return null; + } + return featuresLists.get(mAngleOptInIndex).split(":"); + } + /** * Return the driver package name to use. Return null for system driver. */ - private static String chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai) { + private String chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai) { final String productionDriver = SystemProperties.get(PROPERTY_GFX_DRIVER_PRODUCTION); final boolean hasProductionDriver = productionDriver != null && !productionDriver.isEmpty(); @@ -730,18 +754,18 @@ public class GraphicsEnvironment { final boolean hasPrereleaseDriver = prereleaseDriver != null && !prereleaseDriver.isEmpty(); if (!hasProductionDriver && !hasPrereleaseDriver) { - if (DEBUG) { - Log.v(TAG, - "Neither updatable production driver nor prerelease driver is supported."); - } + Log.v(TAG, "Neither updatable production driver nor prerelease driver is supported."); return null; } - // To minimize risk of driver updates crippling the device beyond user repair, never use an - // updated driver for privileged or non-updated system apps. Presumably pre-installed apps - // were tested thoroughly with the pre-installed driver. + // To minimize risk of driver updates crippling the device beyond user repair, never use the + // updatable drivers for privileged or non-updated system apps. Presumably pre-installed + // apps were tested thoroughly with the system driver. if (ai.isPrivilegedApp() || (ai.isSystemApp() && !ai.isUpdatedSystemApp())) { - if (DEBUG) Log.v(TAG, "Ignoring driver package for privileged/non-updated system app."); + if (DEBUG) { + Log.v(TAG, + "Ignore updatable driver package for privileged/non-updated system app."); + } return null; } @@ -758,13 +782,13 @@ public class GraphicsEnvironment { // 6. UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST switch (coreSettings.getInt(Settings.Global.UPDATABLE_DRIVER_ALL_APPS, 0)) { case UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF: - if (DEBUG) Log.v(TAG, "updatable driver is turned off on this device."); + Log.v(TAG, "The updatable driver is turned off on this device."); return null; case UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRODUCTION_DRIVER: - if (DEBUG) Log.v(TAG, "All apps opt in to use updatable production driver."); + Log.v(TAG, "All apps opt in to use updatable production driver."); return hasProductionDriver ? productionDriver : null; case UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER: - if (DEBUG) Log.v(TAG, "All apps opt in to use updatable prerelease driver."); + Log.v(TAG, "All apps opt in to use updatable prerelease driver."); return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null; case UPDATABLE_DRIVER_GLOBAL_OPT_IN_DEFAULT: default: @@ -775,20 +799,20 @@ public class GraphicsEnvironment { if (getGlobalSettingsString(null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS) .contains(appPackageName)) { - if (DEBUG) Log.v(TAG, "App opts out for updatable production driver."); + Log.v(TAG, "App opts out for updatable production driver."); return null; } if (getGlobalSettingsString( null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS) .contains(appPackageName)) { - if (DEBUG) Log.v(TAG, "App opts in for updatable prerelease driver."); + Log.v(TAG, "App opts in for updatable prerelease driver."); return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null; } // Early return here since the rest logic is only for updatable production Driver. if (!hasProductionDriver) { - if (DEBUG) Log.v(TAG, "Updatable production driver is not supported on the device."); + Log.v(TAG, "Updatable production driver is not supported on the device."); return null; } @@ -801,7 +825,7 @@ public class GraphicsEnvironment { Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST); if (!isOptIn && allowlist.indexOf(UPDATABLE_DRIVER_ALLOWLIST_ALL) != 0 && !allowlist.contains(appPackageName)) { - if (DEBUG) Log.v(TAG, "App is not on the allowlist for updatable production driver."); + Log.v(TAG, "App is not on the allowlist for updatable production driver."); return null; } @@ -811,7 +835,7 @@ public class GraphicsEnvironment { && getGlobalSettingsString( null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST) .contains(appPackageName)) { - if (DEBUG) Log.v(TAG, "App is on the denylist for updatable production driver."); + Log.v(TAG, "App is on the denylist for updatable production driver."); return null; } @@ -821,7 +845,7 @@ public class GraphicsEnvironment { /** * Choose whether the current process should use the builtin or an updated driver. */ - private static boolean chooseDriver( + private boolean chooseDriver( Context context, Bundle coreSettings, PackageManager pm, String packageName, ApplicationInfo ai) { final String driverPackageName = chooseDriverInternal(coreSettings, ai); @@ -834,7 +858,7 @@ public class GraphicsEnvironment { driverPackageInfo = pm.getPackageInfo(driverPackageName, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA); } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "driver package '" + driverPackageName + "' not installed"); + Log.w(TAG, "updatable driver package '" + driverPackageName + "' not installed"); return false; } @@ -843,7 +867,7 @@ public class GraphicsEnvironment { final ApplicationInfo driverAppInfo = driverPackageInfo.applicationInfo; if (driverAppInfo.targetSdkVersion < Build.VERSION_CODES.O) { if (DEBUG) { - Log.w(TAG, "updated driver package is not known to be compatible with O"); + Log.w(TAG, "updatable driver package is not compatible with O"); } return false; } @@ -853,7 +877,7 @@ public class GraphicsEnvironment { if (DEBUG) { // This is the normal case for the pre-installed empty driver package, don't spam if (driverAppInfo.isUpdatedSystemApp()) { - Log.w(TAG, "updated driver package has no compatible native libraries"); + Log.w(TAG, "Updatable driver package has no compatible native libraries"); } } return false; @@ -867,11 +891,8 @@ public class GraphicsEnvironment { .append(abi); final String paths = sb.toString(); final String sphalLibraries = getSphalLibraries(context, driverPackageName); - if (DEBUG) { - Log.v(TAG, - "gfx driver package search path: " + paths - + ", required sphal libraries: " + sphalLibraries); - } + Log.v(TAG, "Updatable driver package search path: " + paths + + ", required sphal libraries: " + sphalLibraries); setDriverPathAndSphalLibraries(paths, sphalLibraries); if (driverAppInfo.metaData == null) { @@ -880,7 +901,7 @@ public class GraphicsEnvironment { String driverBuildTime = driverAppInfo.metaData.getString(METADATA_DRIVER_BUILD_TIME); if (driverBuildTime == null || driverBuildTime.length() <= 1) { - Log.v(TAG, "com.android.graphics.driver.build_time is not set"); + Log.w(TAG, "com.android.graphics.driver.build_time is not set"); driverBuildTime = "L0"; } // driver_build_time in the meta-data is in "L<Unix epoch timestamp>" format. e.g. L123456. @@ -904,7 +925,7 @@ public class GraphicsEnvironment { return null; } - private static String getSphalLibraries(Context context, String driverPackageName) { + private String getSphalLibraries(Context context, String driverPackageName) { try { final Context driverContext = context.createPackageContext(driverPackageName, Context.CONTEXT_RESTRICTED); @@ -935,7 +956,13 @@ public class GraphicsEnvironment { private static native void setGpuStats(String driverPackageName, String driverVersionName, long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion); private static native void setAngleInfo(String path, String appPackage, String devOptIn, - FileDescriptor rulesFd, long rulesOffset, long rulesLength); + String[] features, FileDescriptor rulesFd, long rulesOffset, long rulesLength); private static native boolean getShouldUseAngle(String packageName); private static native boolean setInjectLayersPrSetDumpable(); + + /** + * Hint for GraphicsEnvironment that an activity is launching on the process. + * Then the app process is allowed to send stats to GpuStats module. + */ + public static native void hintActivityLaunch(); } diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index a92d91bdefc5..e996809f1299 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -98,6 +98,8 @@ interface IPowerManager boolean isAmbientDisplaySuppressedForToken(String token); // returns whether ambient display is suppressed by any app with any token. boolean isAmbientDisplaySuppressed(); + // returns whether ambient display is suppressed by the given app with the given token. + boolean isAmbientDisplaySuppressedForTokenByApp(String token, int appUid); // Forces the system to suspend even if there are held wakelocks. boolean forceSuspend(); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 3265829e5061..50f0c28cb8f8 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -2191,6 +2191,27 @@ public final class PowerManager { } /** + * Returns true if ambient display is suppressed by the given {@code appUid} with the given + * {@code token}. + * + * <p>This method will return false if {@link #isAmbientDisplayAvailable()} is false. + * + * @param token The identifier of the ambient display suppression. + * @param appUid The uid of the app that suppressed ambient display. + * @hide + */ + @RequiresPermission(allOf = { + android.Manifest.permission.READ_DREAM_STATE, + android.Manifest.permission.READ_DREAM_SUPPRESSION }) + public boolean isAmbientDisplaySuppressedForTokenByApp(@NonNull String token, int appUid) { + try { + return mService.isAmbientDisplaySuppressedForTokenByApp(token, appUid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the reason the phone was last shutdown. Calling app must have the * {@link android.Manifest.permission#DEVICE_POWER} permission to request this information. * @return Reason for shutdown as an int, {@link #SHUTDOWN_REASON_UNKNOWN} if the file could diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java index b654707a683b..35e7bad83736 100644 --- a/core/java/android/os/ServiceManager.java +++ b/core/java/android/os/ServiceManager.java @@ -235,6 +235,21 @@ public final class ServiceManager { } /** + * Returns the list of declared instances for an interface. + * + * @return true if the service is declared somewhere (eg. VINTF manifest) and + * waitForService should always be able to return the service. + */ + public static String[] getDeclaredInstances(@NonNull String iface) { + try { + return getIServiceManager().getDeclaredInstances(iface); + } catch (RemoteException e) { + Log.e(TAG, "error in getDeclaredInstances", e); + return null; + } + } + + /** * Returns the specified service from the service manager. * * If the service is not running, servicemanager will attempt to start it, and this function diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java index 91b56fbbc38e..b70b6b5d209e 100644 --- a/core/java/android/os/ServiceManagerNative.java +++ b/core/java/android/os/ServiceManagerNative.java @@ -90,6 +90,10 @@ class ServiceManagerProxy implements IServiceManager { return mServiceManager.isDeclared(name); } + public String[] getDeclaredInstances(String iface) throws RemoteException { + return mServiceManager.getDeclaredInstances(iface); + } + public void registerClientCallback(String name, IBinder service, IClientCallback cb) throws RemoteException { throw new RemoteException(); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 07867e2a9d9b..1dbf95f7db86 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7663,6 +7663,8 @@ public final class Settings { * @hide */ @UnsupportedAppUsage + @TestApi + @SuppressLint("NoSettingsProvider") public static final String SELECTED_SPELL_CHECKER = "selected_spell_checker"; /** @@ -7673,6 +7675,8 @@ public final class Settings { * @hide */ @UnsupportedAppUsage + @TestApi + @SuppressLint("NoSettingsProvider") public static final String SELECTED_SPELL_CHECKER_SUBTYPE = "selected_spell_checker_subtype"; @@ -12325,6 +12329,15 @@ public final class Settings { "angle_allowlist"; /** + * Lists of ANGLE EGL features for debugging. + * Each list of features is separated by a comma, each feature in each list is separated by + * a colon. + * e.g. feature1:feature2:feature3,feature1:feature3:feature5 + * @hide + */ + public static final String ANGLE_EGL_FEATURES = "angle_egl_features"; + + /** * Show the "ANGLE In Use" dialog box to the user when ANGLE is the OpenGL driver. * The value is a boolean (1 or 0). * @hide @@ -14464,6 +14477,15 @@ public final class Settings { */ public static final String NR_NSA_TRACKING_SCREEN_OFF_MODE = "nr_nsa_tracking_screen_off_mode"; + + /** + * Whether to show People Space. + * Values are: + * 0: Disabled (default) + * 1: Enabled + * @hide + */ + public static final String SHOW_PEOPLE_SPACE = "show_people_space"; } /** diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 19860eb45fbf..0f46ffcb2d7f 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -879,7 +879,7 @@ public abstract class WallpaperService extends Service { com.android.internal.R.style.Animation_Wallpaper; InputChannel inputChannel = new InputChannel(); - if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE, + if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE, mDisplay.getDisplayId(), mWinFrames.frame, mWinFrames.contentInsets, mWinFrames.stableInsets, mWinFrames.displayCutout, inputChannel, mInsetsState, mTempControls) < 0) { @@ -903,7 +903,7 @@ public abstract class WallpaperService extends Service { } final int relayoutResult = mSession.relayout( - mWindow, mWindow.mSeq, mLayout, mWidth, mHeight, + mWindow, mLayout, mWidth, mHeight, View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl, mInsetsState, mTempControls, mSurfaceSize, mTmpSurfaceControl); if (mSurfaceControl.isValid()) { diff --git a/core/java/android/text/StyledTextShaper.java b/core/java/android/text/StyledTextShaper.java new file mode 100644 index 000000000000..bf906143bc56 --- /dev/null +++ b/core/java/android/text/StyledTextShaper.java @@ -0,0 +1,67 @@ +/* + * 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.text; + +import android.annotation.NonNull; +import android.graphics.Paint; +import android.graphics.text.PositionedGlyphs; +import android.graphics.text.TextShaper; + +import java.util.List; + +/** + * Provides text shaping for multi-styled text. + * + * @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint) + * @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint) + * @see StyledTextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, TextPaint) + */ +public class StyledTextShaper { + private StyledTextShaper() {} + + + /** + * Shape multi-styled text. + * + * @param text a styled text. + * @param start a start index of shaping target in the text. + * @param count a length of shaping target in the text. + * @param dir a text direction. + * @param paint a paint + * @return a shape result. + */ + public static @NonNull List<PositionedGlyphs> shapeText( + @NonNull CharSequence text, int start, int count, + @NonNull TextDirectionHeuristic dir, @NonNull TextPaint paint) { + MeasuredParagraph mp = MeasuredParagraph.buildForBidi( + text, start, start + count, dir, null); + TextLine tl = TextLine.obtain(); + try { + tl.set(paint, text, start, start + count, + mp.getParagraphDir(), + mp.getDirections(start, start + count), + false /* tabstop is not supported */, + null, + -1, -1 // ellipsis is not supported. + ); + return tl.shape(); + } finally { + TextLine.recycle(tl); + } + } + +} diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 3c51fa765263..b82683260985 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -23,6 +23,8 @@ import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; +import android.graphics.text.PositionedGlyphs; +import android.graphics.text.TextShaper; import android.os.Build; import android.text.Layout.Directions; import android.text.Layout.TabStops; @@ -35,6 +37,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import java.util.ArrayList; +import java.util.List; /** * Represents a line of styled text, for measuring in visual order and @@ -307,6 +310,36 @@ public class TextLine { } /** + * Shape the TextLine. + */ + List<PositionedGlyphs> shape() { + List<PositionedGlyphs> glyphs = new ArrayList<>(); + float horizontal = 0; + float x = 0; + final int runCount = mDirections.getRunCount(); + for (int runIndex = 0; runIndex < runCount; runIndex++) { + final int runStart = mDirections.getRunStart(runIndex); + if (runStart > mLen) break; + final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen); + final boolean runIsRtl = mDirections.isRunRtl(runIndex); + + int segStart = runStart; + for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { + if (j == runLimit || charAt(j) == TAB_CHAR) { + horizontal += shapeRun(glyphs, segStart, j, runIsRtl, x + horizontal, + runIndex != (runCount - 1) || j != mLen); + + if (j != runLimit) { // charAt(j) == TAB_CHAR + horizontal = mDir * nextTab(horizontal * mDir); + } + segStart = j + 1; + } + } + } + return glyphs; + } + + /** * Returns the signed graphical offset from the leading margin. * * Following examples are all for measuring offset=3. LX(e.g. L0, L1, ...) denotes a @@ -483,12 +516,12 @@ public class TextLine { if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { float w = -measureRun(start, limit, limit, runIsRtl, null); - handleRun(start, limit, limit, runIsRtl, c, x + w, top, + handleRun(start, limit, limit, runIsRtl, c, null, x + w, top, y, bottom, null, false); return w; } - return handleRun(start, limit, limit, runIsRtl, c, x, top, + return handleRun(start, limit, limit, runIsRtl, c, null, x, top, y, bottom, null, needWidth); } @@ -507,9 +540,34 @@ public class TextLine { */ private float measureRun(int start, int offset, int limit, boolean runIsRtl, FontMetricsInt fmi) { - return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true); + return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true); + } + + /** + * Shape a unidirectional (but possibly multi-styled) run of text. + * + * @param glyphs the output positioned glyphs list + * @param start the line-relative start + * @param limit the line-relative limit + * @param runIsRtl true if the run is right-to-left + * @param x the position of the run that is closest to the leading margin + * @param needWidth true if the width value is required. + * @return the signed width of the run, based on the paragraph direction. + * Only valid if needWidth is true. + */ + private float shapeRun(List<PositionedGlyphs> glyphs, int start, + int limit, boolean runIsRtl, float x, boolean needWidth) { + + if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { + float w = -measureRun(start, limit, limit, runIsRtl, null); + handleRun(start, limit, limit, runIsRtl, null, glyphs, x + w, 0, 0, 0, null, false); + return w; + } + + return handleRun(start, limit, limit, runIsRtl, null, glyphs, x, 0, 0, 0, null, needWidth); } + /** * Walk the cursor through this line, skipping conjuncts and * zero-width characters. @@ -841,6 +899,7 @@ public class TextLine { * @param end the end of the text * @param runIsRtl true if the run is right-to-left * @param c the canvas, can be null if rendering is not needed + * @param glyphs the output positioned glyph list, can be null if not necessary * @param x the edge of the run closest to the leading margin * @param top the top of the line * @param y the baseline @@ -854,7 +913,7 @@ public class TextLine { */ private float handleText(TextPaint wp, int start, int end, int contextStart, int contextEnd, boolean runIsRtl, - Canvas c, float x, int top, int y, int bottom, + Canvas c, List<PositionedGlyphs> glyphs, float x, int top, int y, int bottom, FontMetricsInt fmi, boolean needWidth, int offset, @Nullable ArrayList<DecorationInfo> decorations) { @@ -878,16 +937,20 @@ public class TextLine { totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset); } - if (c != null) { - final float leftX, rightX; - if (runIsRtl) { - leftX = x - totalWidth; - rightX = x; - } else { - leftX = x; - rightX = x + totalWidth; - } + final float leftX, rightX; + if (runIsRtl) { + leftX = x - totalWidth; + rightX = x; + } else { + leftX = x; + rightX = x + totalWidth; + } + + if (glyphs != null) { + shapeTextRun(glyphs, wp, start, end, contextStart, contextEnd, runIsRtl, leftX); + } + if (c != null) { if (wp.bgColor != 0) { int previousColor = wp.getColor(); Paint.Style previousStyle = wp.getStyle(); @@ -1072,6 +1135,7 @@ public class TextLine { * @param limit the limit of the run * @param runIsRtl true if the run is right-to-left * @param c the canvas, can be null + * @param glyphs the output positioned glyphs, can be null * @param x the end of the run closest to the leading margin * @param top the top of the line * @param y the baseline @@ -1082,7 +1146,8 @@ public class TextLine { * valid if needWidth is true */ private float handleRun(int start, int measureLimit, - int limit, boolean runIsRtl, Canvas c, float x, int top, int y, + int limit, boolean runIsRtl, Canvas c, + List<PositionedGlyphs> glyphs, float x, int top, int y, int bottom, FontMetricsInt fmi, boolean needWidth) { if (measureLimit < start || measureLimit > limit) { @@ -1115,7 +1180,7 @@ public class TextLine { wp.set(mPaint); wp.setStartHyphenEdit(adjustStartHyphenEdit(start, wp.getStartHyphenEdit())); wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit())); - return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top, + return handleText(wp, start, limit, start, limit, runIsRtl, c, glyphs, x, top, y, bottom, fmi, needWidth, measureLimit, null); } @@ -1196,8 +1261,8 @@ public class TextLine { adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit())); activePaint.setEndHyphenEdit( adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit())); - x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, - top, y, bottom, fmi, needWidth || activeEnd < measureLimit, + x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, + glyphs, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mDecorations); activeStart = j; @@ -1223,7 +1288,7 @@ public class TextLine { adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit())); activePaint.setEndHyphenEdit( adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit())); - x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, + x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, glyphs, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mDecorations); } @@ -1260,6 +1325,45 @@ public class TextLine { } /** + * Shape a text run with the set-up paint. + * + * @param glyphs the output positioned glyphs list + * @param paint the paint used to render the text + * @param start the start of the run + * @param end the end of the run + * @param contextStart the start of context for the run + * @param contextEnd the end of the context for the run + * @param runIsRtl true if the run is right-to-left + * @param x the x position of the left edge of the run + */ + private void shapeTextRun(List<PositionedGlyphs> glyphs, TextPaint paint, + int start, int end, int contextStart, int contextEnd, boolean runIsRtl, float x) { + + int count = end - start; + int contextCount = contextEnd - contextStart; + if (mCharsValid) { + glyphs.add(TextShaper.shapeTextRun( + mChars, + start, count, + contextStart, contextCount, + x, 0f, + runIsRtl, + paint + )); + } else { + glyphs.add(TextShaper.shapeTextRun( + mText, + mStart + start, count, + mStart + contextStart, contextCount, + x, 0f, + runIsRtl, + paint + )); + } + } + + + /** * Returns the next tab position. * * @param h the (unsigned) offset from the leading margin diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index 2ded473930f7..7a117f14d9b5 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -227,7 +227,7 @@ public final class Log { * @param level The level to check. * @return Whether or not that this is allowed to be logged. * @throws IllegalArgumentException is thrown if the tag.length() > 23 - * for Nougat (7.0) releases (API <= 23) and prior, there is no + * for Nougat (7.0) and prior releases (API <= 25), there is no * tag limit of concern after this API level. */ @FastNative diff --git a/core/java/android/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java new file mode 100644 index 000000000000..865d5608a40a --- /dev/null +++ b/core/java/android/util/imetracing/ImeTracing.java @@ -0,0 +1,114 @@ +/* + * 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.imetracing; + +import android.app.ActivityThread; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.os.ShellCommand; +import android.util.Log; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.view.IInputMethodManager; + +/** + * + * An abstract class that declares the methods for ime trace related operations - enable trace, + * schedule trace and add new trace to buffer. Both the client and server side classes can use + * it by getting an implementation through {@link ImeTracing#getInstance()}. + * + * @hide + */ +public abstract class ImeTracing { + + static final String TAG = "imeTracing"; + public static final String PROTO_ARG = "--proto-com-android-imetracing"; + + private static ImeTracing sInstance; + static boolean sEnabled = false; + IInputMethodManager mService; + + ImeTracing() throws ServiceNotFoundException { + mService = IInputMethodManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)); + } + + /** + * Returns an instance of {@link ImeTracingServerImpl} when called from a server side class + * and an instance of {@link ImeTracingClientImpl} when called from a client side class. + * Useful to schedule a dump for next frame or save a dump when certain methods are called. + * + * @return Instance of one of the children classes of {@link ImeTracing} + */ + public static ImeTracing getInstance() { + if (sInstance == null) { + try { + sInstance = isSystemProcess() + ? new ImeTracingServerImpl() : new ImeTracingClientImpl(); + } catch (RemoteException | ServiceNotFoundException e) { + Log.e(TAG, "Exception while creating ImeTracing instance", e); + } + } + return sInstance; + } + + /** + * Sends request to start proto dump to {@link ImeTracingServerImpl} when called from a + * server process and to {@link ImeTracingClientImpl} when called from a client process. + */ + public abstract void triggerDump(); + + /** + * @param proto dump to be added to the buffer + */ + public abstract void addToBuffer(ProtoOutputStream proto); + + /** + * @param shell The shell command to process + * @return {@code 0} if the command was successfully processed, {@code -1} otherwise + */ + public abstract int onShellCommand(ShellCommand shell); + + /** + * Sets whether ime tracing is enabled. + * + * @param enabled Tells whether ime tracing should be enabled or disabled. + */ + public void setEnabled(boolean enabled) { + sEnabled = enabled; + } + + /** + * @return {@code true} if dumping is enabled, {@code false} otherwise. + */ + public boolean isEnabled() { + return sEnabled; + } + + /** + * @return {@code true} if tracing is available, {@code false} otherwise. + */ + public boolean isAvailable() { + return mService != null; + } + + private static boolean isSystemProcess() { + return ActivityThread.isSystem(); + } +} diff --git a/core/java/android/util/imetracing/ImeTracingClientImpl.java b/core/java/android/util/imetracing/ImeTracingClientImpl.java new file mode 100644 index 000000000000..e5d7d3380d02 --- /dev/null +++ b/core/java/android/util/imetracing/ImeTracingClientImpl.java @@ -0,0 +1,71 @@ +/* + * 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.imetracing; + +import android.os.RemoteException; +import android.os.ServiceManager.ServiceNotFoundException; +import android.os.ShellCommand; +import android.util.Log; +import android.util.proto.ProtoOutputStream; +import android.view.inputmethod.InputMethodManager; + +/** + * @hide + */ +class ImeTracingClientImpl extends ImeTracing { + + private boolean mDumpInProgress; + private final Object mDumpInProgressLock = new Object(); + + ImeTracingClientImpl() throws ServiceNotFoundException, RemoteException { + sEnabled = mService.isImeTraceEnabled(); + } + + @Override + public void addToBuffer(ProtoOutputStream proto) { + } + + @Override + public int onShellCommand(ShellCommand shell) { + return -1; + } + + @Override + public void triggerDump() { + if (isAvailable() && isEnabled()) { + boolean doDump = false; + synchronized (mDumpInProgressLock) { + if (!mDumpInProgress) { + mDumpInProgress = true; + doDump = true; + } + } + + if (doDump) { + try { + ProtoOutputStream proto = new ProtoOutputStream(); + InputMethodManager.dumpProto(proto); + mService.startProtoDump(proto.getBytes()); + } catch (RemoteException e) { + Log.e(TAG, "Exception while sending ime-related client dump to server", e); + } finally { + mDumpInProgress = false; + } + } + } + } +} diff --git a/core/java/android/util/imetracing/ImeTracingServerImpl.java b/core/java/android/util/imetracing/ImeTracingServerImpl.java new file mode 100644 index 000000000000..350cf5721148 --- /dev/null +++ b/core/java/android/util/imetracing/ImeTracingServerImpl.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util.imetracing; + +import static android.os.Build.IS_USER; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.MAGIC_NUMBER; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.MAGIC_NUMBER_H; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.MAGIC_NUMBER_L; + +import android.os.RemoteException; +import android.os.ServiceManager.ServiceNotFoundException; +import android.os.ShellCommand; +import android.util.Log; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.TraceBuffer; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * @hide + */ +class ImeTracingServerImpl extends ImeTracing { + private static final String TRACE_FILENAME = "/data/misc/wmtrace/ime_trace.pb"; + private static final int BUFFER_CAPACITY = 4096 * 1024; + + // Needed for winscope to auto-detect the dump type. Explained further in + // core.proto.android.view.inputmethod.inputmethodeditortrace.proto + private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; + + private final TraceBuffer mBuffer; + private final File mTraceFile; + private final Object mEnabledLock = new Object(); + + ImeTracingServerImpl() throws ServiceNotFoundException { + mBuffer = new TraceBuffer<>(BUFFER_CAPACITY); + mTraceFile = new File(TRACE_FILENAME); + } + + /** + * The provided dump is added to the current dump buffer {@link ImeTracingServerImpl#mBuffer}. + * + * @param proto dump to be added to the buffer + */ + @Override + public void addToBuffer(ProtoOutputStream proto) { + if (isAvailable() && isEnabled()) { + mBuffer.add(proto); + } + } + + /** + * Responds to a shell command of the format "adb shell cmd input_method ime tracing <command>" + * + * @param shell The shell command to process + * @return {@code 0} if the command was valid and successfully processed, {@code -1} otherwise + */ + @Override + public int onShellCommand(ShellCommand shell) { + PrintWriter pw = shell.getOutPrintWriter(); + String cmd = shell.getNextArgRequired(); + switch (cmd) { + case "start": + startTrace(pw); + return 0; + case "stop": + stopTrace(pw); + return 0; + default: + pw.println("Unknown command: " + cmd); + pw.println("Input method trace options:"); + pw.println(" start: Start tracing"); + pw.println(" stop: Stop tracing"); + return -1; + } + } + + @Override + public void triggerDump() { + if (isAvailable() && isEnabled()) { + try { + mService.startProtoDump(null); + } catch (RemoteException e) { + Log.e(TAG, "Exception while triggering proto dump", e); + } + } + } + + private void writeTraceToFileLocked() { + try { + ProtoOutputStream proto = new ProtoOutputStream(); + proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); + mBuffer.writeTraceToFile(mTraceFile, proto); + } catch (IOException e) { + Log.e(TAG, "Unable to write buffer to file", e); + } + } + + @GuardedBy("mEnabledLock") + private void startTrace(PrintWriter pw) { + if (IS_USER) { + Log.w(TAG, "Warn: Tracing is not supported on user builds."); + return; + } + + synchronized (mEnabledLock) { + if (isAvailable() && isEnabled()) { + Log.w(TAG, "Warn: Tracing is already started."); + return; + } + + pw.println("Starting tracing to " + mTraceFile + "."); + sEnabled = true; + mBuffer.resetBuffer(); + } + } + + @GuardedBy("mEnabledLock") + private void stopTrace(PrintWriter pw) { + if (IS_USER) { + Log.w(TAG, "Warn: Tracing is not supported on user builds."); + return; + } + + synchronized (mEnabledLock) { + if (!isAvailable() || !isEnabled()) { + Log.w(TAG, "Warn: Tracing is not available or not started."); + return; + } + + pw.println("Stopping tracing and writing traces to " + mTraceFile + "."); + sEnabled = false; + writeTraceToFileLocked(); + mBuffer.resetBuffer(); + } + } +} diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 042808a4cad2..be52667625a3 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -660,6 +660,8 @@ public final class Choreographer { ThreadedRenderer.setFPSDivisor(divisor); } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link " + + "#postFrameCallback} instead") void doFrame(long frameTimeNanos, int frame, long frameTimelineVsyncId) { final long startNanos; synchronized (mLock) { diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index 51474d3c39c8..430e2cf2bf21 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -17,6 +17,7 @@ package android.view; import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; import android.os.Looper; import android.os.MessageQueue; import android.util.Log; @@ -157,6 +158,8 @@ public abstract class DisplayEventReceiver { * @param frameTimelineVsyncId The frame timeline vsync id, used to correlate a frame * produced by HWUI with the timeline data stored in Surface Flinger. */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link " + + "Choreographer#postFrameCallback} instead") public void onVsync(long timestampNanos, long physicalDisplayId, int frame, long frameTimelineVsyncId) { } @@ -200,6 +203,8 @@ public abstract class DisplayEventReceiver { // Called from native code. @SuppressWarnings("unused") + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link " + + "Choreographer#postFrameCallback} instead") private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame, long frameTimelineVsyncId) { onVsync(timestampNanos, physicalDisplayId, frame, frameTimelineVsyncId); diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index 94e641c62b25..193e674dd1b0 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -120,12 +120,6 @@ oneway interface IWindow { void updatePointerIcon(float x, float y); /** - * System chrome visibility changes - */ - void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, - int localValue, int localChanges); - - /** * Called for non-application windows when the enter animation has completed. */ void dispatchWindowShown(); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 8e875d7a889d..daab70ae336f 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -378,11 +378,6 @@ interface IWindowManager boolean requestAssistScreenshot(IAssistDataReceiver receiver); /** - * Called by the status bar to notify Views of changes to System UI visiblity. - */ - oneway void statusBarVisibilityChanged(int displayId, int visibility); - - /** * Called by System UI to notify Window Manager to hide transient bars. */ oneway void hideTransientBars(int displayId); @@ -757,4 +752,10 @@ interface IWindowManager */ void requestScrollCapture(int displayId, IBinder behindClient, int taskId, IScrollCaptureController controller); + + /** + * Holds the WM lock for the specified amount of milliseconds. + * Intended for use by the tests that need to imitate lock contention. + */ + void holdLock(in int durationMs); } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 18c87c092569..69a5fafbb0db 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -44,17 +44,17 @@ import java.util.List; * {@hide} */ interface IWindowSession { - int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs, + int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets, out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel, out InsetsState insetsState, out InsetsSourceControl[] activeControls); - int addToDisplayAsUser(IWindow window, int seq, in WindowManager.LayoutParams attrs, + int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, in int userId, out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets, out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel, out InsetsState insetsState, out InsetsSourceControl[] activeControls); - int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs, + int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, out Rect outContentInsets, out Rect outStableInsets, out InsetsState insetsState); @UnsupportedAppUsage @@ -68,7 +68,6 @@ interface IWindowSession { * to draw the window's contents. * * @param window The window being modified. - * @param seq Ordering sequence number. * @param attrs If non-null, new attributes to apply to the window. * @param requestedWidth The width the window wants to be. * @param requestedHeight The height the window wants to be. @@ -106,7 +105,7 @@ interface IWindowSession { * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS}, * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}. */ - int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs, + int relayout(IWindow window, in WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, long frameNumber, out ClientWindowFrames outFrames, out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl, diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index 92772c1d7a44..efc0bd2785f4 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -16,16 +16,23 @@ package android.view; +import static android.view.ImeFocusControllerProto.HAS_IME_FOCUS; +import static android.view.ImeFocusControllerProto.NEXT_SERVED_VIEW; +import static android.view.ImeFocusControllerProto.SERVED_VIEW; + import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.UiThread; import android.util.Log; +import android.util.proto.ProtoOutputStream; import android.view.inputmethod.InputMethodManager; import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; +import java.util.Objects; + /** * Responsible for IME focus handling inside {@link ViewRootImpl}. * @hide @@ -280,4 +287,12 @@ public final class ImeFocusController { boolean hasImeFocus() { return mHasImeFocus; } + + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(HAS_IME_FOCUS, mHasImeFocus); + proto.write(SERVED_VIEW, Objects.toString(mServedView)); + proto.write(NEXT_SERVED_VIEW, Objects.toString(mNextServedView)); + proto.end(token); + } } diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 82f60366a814..dd1a19458e1d 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -16,6 +16,9 @@ package android.view; +import static android.view.ImeInsetsSourceConsumerProto.FOCUSED_EDITOR; +import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER; +import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL; import static android.view.InsetsController.AnimationType; import static android.view.InsetsState.ITYPE_IME; @@ -24,6 +27,7 @@ import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.Parcel; import android.text.TextUtils; +import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl.Transaction; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; @@ -111,7 +115,6 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { public @ShowResult int requestShow(boolean fromIme) { // TODO: ResultReceiver for IME. // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag. - if (getControl() == null) { // If control is null, schedule to show IME when control is available. mIsRequestedVisibleAwaitingControl = true; @@ -227,6 +230,17 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray()); } + @Override + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + super.dumpDebug(proto, INSETS_SOURCE_CONSUMER); + if (mFocusedEditor != null) { + mFocusedEditor.dumpDebug(proto, FOCUSED_EDITOR); + } + proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl); + proto.end(token); + } + private InputMethodManager getImm() { return mController.getHost().getInputMethodManager(); } diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 6ffd892e4351..71899fab554c 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -17,6 +17,14 @@ package android.view; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.InsetsAnimationControlImplProto.CURRENT_ALPHA; +import static android.view.InsetsAnimationControlImplProto.IS_CANCELLED; +import static android.view.InsetsAnimationControlImplProto.IS_FINISHED; +import static android.view.InsetsAnimationControlImplProto.PENDING_ALPHA; +import static android.view.InsetsAnimationControlImplProto.PENDING_FRACTION; +import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS; +import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH; +import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX; import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static android.view.InsetsController.AnimationType; import static android.view.InsetsController.DEBUG; @@ -38,6 +46,8 @@ import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.SparseSetArray; +import android.util.imetracing.ImeTracing; +import android.util.proto.ProtoOutputStream; import android.view.InsetsState.InternalInsetsSide; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; @@ -48,6 +58,7 @@ import android.view.animation.Interpolator; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; +import java.util.Objects; /** * Implements {@link WindowInsetsAnimationController} @@ -122,6 +133,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mAnimationType = animationType; mController.startAnimation(this, listener, types, mAnimation, new Bounds(mHiddenInsets, mShownInsets)); + + if ((mTypes & WindowInsets.Type.ime()) != 0) { + ImeTracing.getInstance().triggerDump(); + } } private boolean calculatePerceptible(Insets currentInsets, float currentAlpha) { @@ -285,6 +300,20 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll return mAnimation; } + @Override + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(IS_CANCELLED, mCancelled); + proto.write(IS_FINISHED, mFinished); + proto.write(TMP_MATRIX, Objects.toString(mTmpMatrix)); + proto.write(PENDING_INSETS, Objects.toString(mPendingInsets)); + proto.write(PENDING_FRACTION, mPendingFraction); + proto.write(SHOWN_ON_FINISH, mShownOnFinish); + proto.write(CURRENT_ALPHA, mCurrentAlpha); + proto.write(PENDING_ALPHA, mPendingAlpha); + proto.end(token); + } + WindowInsetsAnimationControlListener getListener() { return mListener; } diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java index 0711c3e166d8..0275b521a2a2 100644 --- a/core/java/android/view/InsetsAnimationControlRunner.java +++ b/core/java/android/view/InsetsAnimationControlRunner.java @@ -16,6 +16,7 @@ package android.view; +import android.util.proto.ProtoOutputStream; import android.view.InsetsController.AnimationType; import android.view.InsetsState.InternalInsetsType; import android.view.WindowInsets.Type.InsetsType; @@ -53,4 +54,14 @@ public interface InsetsAnimationControlRunner { * @return The animation type this runner is running. */ @AnimationType int getAnimationType(); + + /** + * + * Export the state of classes that implement this interface into a protocol buffer + * output stream. + * + * @param proto Stream to write the state to + * @param fieldId FieldId of the implementation class + */ + void dumpDebug(ProtoOutputStream proto, long fieldId); } diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 123604489da4..cc3cd278b267 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.Trace; import android.util.Log; import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; import android.view.InsetsController.AnimationType; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; @@ -122,6 +123,12 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro @Override @UiThread + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + mControl.dumpDebug(proto, fieldId); + } + + @Override + @UiThread public int getTypes() { return mControl.getTypes(); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 92eade3affaa..652781a310b9 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.InsetsControllerProto.CONTROL; +import static android.view.InsetsControllerProto.STATE; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.toInternalType; @@ -41,6 +43,8 @@ import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; +import android.util.imetracing.ImeTracing; +import android.util.proto.ProtoOutputStream; import android.view.InsetsSourceConsumer.ShowResult; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; @@ -298,6 +302,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void onReady(WindowInsetsAnimationController controller, int types) { + if ((types & ime()) != 0) { + ImeTracing.getInstance().triggerDump(); + } + mController = controller; if (DEBUG) Log.d(TAG, "default animation onReady types: " + types); @@ -812,6 +820,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public void show(@InsetsType int types, boolean fromIme) { + if (fromIme) { + ImeTracing.getInstance().triggerDump(); + } // Handle pending request ready in case there was one set. if (fromIme && mPendingImeControlRequest != null) { PendingControlRequest pendingRequest = mPendingImeControlRequest; @@ -860,6 +871,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } void hide(@InsetsType int types, boolean fromIme) { + if (fromIme) { + ImeTracing.getInstance().triggerDump(); + } int typesReady = 0; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { @@ -894,6 +908,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation listener.onCancelled(null); return; } + if (fromIme) { + ImeTracing.getInstance().triggerDump(); + } controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs, interpolator, animationType, getLayoutInsetsDuringAnimationMode(types), @@ -1292,6 +1309,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void hideDirectly( @InsetsType int types, boolean animationFinished, @AnimationType int animationType) { + if ((types & ime()) != 0) { + ImeTracing.getInstance().triggerDump(); + } final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished, animationType); @@ -1299,6 +1319,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } private void showDirectly(@InsetsType int types) { + if ((types & ime()) != 0) { + ImeTracing.getInstance().triggerDump(); + } final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */); @@ -1318,6 +1341,16 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mState.dump(prefix + " ", pw); } + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + mState.dumpDebug(proto, STATE); + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + InsetsAnimationControlRunner runner = mRunningAnimations.get(i).runner; + runner.dumpDebug(proto, CONTROL); + } + proto.end(token); + } + @VisibleForTesting @Override public void startAnimation(InsetsAnimationControlImpl controller, @@ -1388,7 +1421,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } @Override - public @Appearance int getSystemBarsBehavior() { + public @Behavior int getSystemBarsBehavior() { return mHost.getSystemBarsBehavior(); } diff --git a/core/java/android/view/InsetsFlags.java b/core/java/android/view/InsetsFlags.java index 385b0bf960ef..5a64a5d5b3a6 100644 --- a/core/java/android/view/InsetsFlags.java +++ b/core/java/android/view/InsetsFlags.java @@ -16,13 +16,6 @@ package android.view; -import static android.view.View.NAVIGATION_BAR_TRANSLUCENT; -import static android.view.View.NAVIGATION_BAR_TRANSPARENT; -import static android.view.View.STATUS_BAR_TRANSLUCENT; -import static android.view.View.STATUS_BAR_TRANSPARENT; -import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; -import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; -import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; @@ -76,44 +69,4 @@ public class InsetsFlags { name = "SHOW_TRANSIENT_BARS_BY_SWIPE") }) public @Behavior int behavior; - - /** - * Converts system UI visibility to appearance. - * - * @param systemUiVisibility the system UI visibility to be converted. - * @return the outcome {@link Appearance} - */ - public static @Appearance int getAppearance(int systemUiVisibility) { - int appearance = 0; - appearance |= convertFlag(systemUiVisibility, SYSTEM_UI_FLAG_LOW_PROFILE, - APPEARANCE_LOW_PROFILE_BARS); - appearance |= convertFlag(systemUiVisibility, SYSTEM_UI_FLAG_LIGHT_STATUS_BAR, - APPEARANCE_LIGHT_STATUS_BARS); - appearance |= convertFlag(systemUiVisibility, SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, - APPEARANCE_LIGHT_NAVIGATION_BARS); - appearance |= convertNoFlag(systemUiVisibility, - STATUS_BAR_TRANSLUCENT | STATUS_BAR_TRANSPARENT, APPEARANCE_OPAQUE_STATUS_BARS); - appearance |= convertNoFlag(systemUiVisibility, - NAVIGATION_BAR_TRANSLUCENT | NAVIGATION_BAR_TRANSPARENT, - APPEARANCE_OPAQUE_NAVIGATION_BARS); - return appearance; - } - - /** - * Converts the system UI visibility into an appearance flag if the given visibility contains - * the given system UI flag. - */ - private static @Appearance int convertFlag(int systemUiVisibility, int systemUiFlag, - @Appearance int appearance) { - return (systemUiVisibility & systemUiFlag) != 0 ? appearance : 0; - } - - /** - * Converts the system UI visibility into an appearance flag if the given visibility doesn't - * contains the given system UI flag. - */ - private static @Appearance int convertNoFlag(int systemUiVisibility, int systemUiFlag, - @Appearance int appearance) { - return (systemUiVisibility & systemUiFlag) == 0 ? appearance : 0; - } } diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index dbf75705c073..41cc8459a266 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -16,6 +16,10 @@ package android.view; +import static android.view.InsetsSourceProto.FRAME; +import static android.view.InsetsSourceProto.TYPE; +import static android.view.InsetsSourceProto.VISIBLE; +import static android.view.InsetsSourceProto.VISIBLE_FRAME; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; @@ -25,6 +29,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; +import android.util.proto.ProtoOutputStream; import android.view.InsetsState.InternalInsetsType; import java.io.PrintWriter; @@ -183,6 +188,17 @@ public class InsetsSource implements Parcelable { return false; } + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(TYPE, InsetsState.typeToString(mType)); + mFrame.dumpDebug(proto, FRAME); + if (mVisibleFrame != null) { + mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME); + } + proto.write(VISIBLE, mVisible); + proto.end(token); + } + public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType)); diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index ba40459692f7..d7ceaf792198 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -19,6 +19,13 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.AnimationType; import static android.view.InsetsController.DEBUG; +import static android.view.InsetsSourceConsumerProto.HAS_WINDOW_FOCUS; +import static android.view.InsetsSourceConsumerProto.INTERNAL_INSETS_TYPE; +import static android.view.InsetsSourceConsumerProto.IS_REQUESTED_VISIBLE; +import static android.view.InsetsSourceConsumerProto.PENDING_FRAME; +import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME; +import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL; +import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.getDefaultVisibility; import static android.view.InsetsState.toPublicType; @@ -28,6 +35,8 @@ import android.annotation.IntDef; import android.annotation.Nullable; import android.graphics.Rect; import android.util.Log; +import android.util.imetracing.ImeTracing; +import android.util.proto.ProtoOutputStream; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetsType; @@ -319,6 +328,9 @@ public class InsetsSourceConsumer { @VisibleForTesting(visibility = PACKAGE) public boolean notifyAnimationFinished() { + if (mType == ITYPE_IME) { + ImeTracing.getInstance().triggerDump(); + } if (mPendingFrame != null) { InsetsSource source = mState.getSource(mType); source.setFrame(mPendingFrame); @@ -360,4 +372,21 @@ public class InsetsSourceConsumer { t.apply(); onPerceptible(mRequestedVisible); } + + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mType)); + proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus); + proto.write(IS_REQUESTED_VISIBLE, mRequestedVisible); + if (mSourceControl != null) { + mSourceControl.dumpDebug(proto, SOURCE_CONTROL); + } + if (mPendingFrame != null) { + mPendingFrame.dumpDebug(proto, PENDING_FRAME); + } + if (mPendingVisibleFrame != null) { + mPendingVisibleFrame.dumpDebug(proto, PENDING_VISIBLE_FRAME); + } + proto.end(token); + } } diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 51b49214387a..b45bd3869f64 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -16,10 +16,17 @@ package android.view; +import static android.graphics.PointProto.X; +import static android.graphics.PointProto.Y; +import static android.view.InsetsSourceControlProto.LEASH; +import static android.view.InsetsSourceControlProto.POSITION; +import static android.view.InsetsSourceControlProto.TYPE; + import android.annotation.Nullable; import android.graphics.Point; import android.os.Parcel; import android.os.Parcelable; +import android.util.proto.ProtoOutputStream; import android.view.InsetsState.InternalInsetsType; import java.io.PrintWriter; @@ -120,4 +127,19 @@ public class InsetsSourceControl implements Parcelable { return new InsetsSourceControl[size]; } }; + + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(TYPE, InsetsState.typeToString(mType)); + + final long surfaceToken = proto.start(POSITION); + proto.write(X, mSurfacePosition.x); + proto.write(Y, mSurfacePosition.y); + proto.end(surfaceToken); + + if (mLeash != null) { + mLeash.dumpDebug(proto, LEASH); + } + proto.end(token); + } } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index c5d0a10bb108..eabb71851303 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.InsetsStateProto.DISPLAY_FRAME; +import static android.view.InsetsStateProto.SOURCES; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE; import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES; import static android.view.WindowInsets.Type.SYSTEM_GESTURES; @@ -41,6 +43,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; import android.util.SparseIntArray; +import android.util.proto.ProtoOutputStream; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; @@ -545,6 +548,16 @@ public class InsetsState implements Parcelable { } } + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + InsetsSource source = mSources[ITYPE_IME]; + if (source != null) { + source.dumpDebug(proto, SOURCES); + } + mDisplayFrame.dumpDebug(proto, DISPLAY_FRAME); + proto.end(token); + } + public static String typeToString(@InternalInsetsType int type) { switch (type) { case ITYPE_STATUS_BAR: diff --git a/core/java/android/view/OnReceiveContentCallback.java b/core/java/android/view/OnReceiveContentCallback.java new file mode 100644 index 000000000000..73bcb93d39d0 --- /dev/null +++ b/core/java/android/view/OnReceiveContentCallback.java @@ -0,0 +1,377 @@ +/* + * 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; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.content.ClipData; +import android.content.ClipDescription; +import android.net.Uri; +import android.os.Bundle; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; +import java.util.Set; + +/** + * Callback for apps to implement handling for insertion of content. "Content" here refers to both + * text and non-text (plain/styled text, HTML, images, videos, audio files, etc). + * + * <p>This callback can be attached to different types of UI components using + * {@link View#setOnReceiveContentCallback}. + * + * <p>For editable {@link android.widget.TextView} components, implementations can extend from + * {@link android.widget.TextViewOnReceiveContentCallback} to reuse default platform behavior for + * handling text. + * + * <p>Example implementation:<br> + * <pre class="prettyprint"> + * public class MyOnReceiveContentCallback implements OnReceiveContentCallback<TextView> { + * + * private static final Set<String> SUPPORTED_MIME_TYPES = Collections.unmodifiableSet( + * Set.of("image/*", "video/*")); + * + * @NonNull + * @Override + * public Set<String> getSupportedMimeTypes() { + * return SUPPORTED_MIME_TYPES; + * } + * + * @Override + * public boolean onReceiveContent(@NonNull TextView view, @NonNull Payload payload) { + * // ... app-specific logic to handle the content in the payload ... + * } + * } + * </pre> + * + * @param <T> The type of {@link View} with which this receiver can be associated. + */ +public interface OnReceiveContentCallback<T extends View> { + /** + * Receive the given content. + * + * <p>This function will only be invoked if the MIME type of the content is in the set of + * types returned by {@link #getSupportedMimeTypes}. + * + * <p>For text, if the view has a selection, the selection should be overwritten by the clip; if + * there's no selection, this method should insert the content at the current cursor position. + * + * <p>For non-text content (e.g. an image), the content may be inserted inline, or it may be + * added as an attachment (could potentially be shown in a completely separate view). + * + * @param view The view where the content insertion was requested. + * @param payload The content to insert and related metadata. + * + * @return Returns true if some or all of the content is accepted for insertion. If accepted, + * actual insertion may be handled asynchronously in the background and may or may not result in + * successful insertion. For example, the app may not end up inserting an accepted item if it + * exceeds the app's size limit for that type of content. + */ + boolean onReceiveContent(@NonNull T view, @NonNull Payload payload); + + /** + * Returns the MIME types that can be handled by this callback. + * + * <p>The {@link #onReceiveContent} callback method will only be invoked if the MIME type of the + * content is in the set of supported types returned here. + * + * <p>Different platform features (e.g. pasting from the clipboard, inserting stickers from the + * keyboard, etc) may use this function to conditionally alter their behavior. For example, the + * keyboard may choose to hide its UI for inserting GIFs if the input field that has focus has + * a {@link OnReceiveContentCallback} set and the MIME types returned from this function don't + * include "image/gif". + * + * <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike formal RFC + * MIME types. As a result, you should always write your MIME types with lower case letters, or + * use {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to lower + * case.</em> + * + * @param view The target view. + * @return An immutable set with the MIME types supported by this callback. The returned MIME + * types may contain wildcards such as "text/*", "image/*", etc. + */ + @SuppressLint("CallbackMethodName") + @NonNull + Set<String> getSupportedMimeTypes(@NonNull T view); + + /** + * Returns true if at least one of the MIME types of the given clip is + * {@link #getSupportedMimeTypes supported} by this receiver. + * + * @hide + */ + default boolean supports(@NonNull T view, @NonNull ClipDescription description) { + for (String supportedMimeType : getSupportedMimeTypes(view)) { + if (description.hasMimeType(supportedMimeType)) { + return true; + } + } + return false; + } + + /** + * Holds all the relevant data for a request to {@link OnReceiveContentCallback}. + */ + final class Payload { + + /** + * Specifies the UI through which content is being inserted. + * + * @hide + */ + @IntDef(prefix = {"SOURCE_"}, value = {SOURCE_CLIPBOARD, SOURCE_INPUT_METHOD, + SOURCE_DRAG_AND_DROP, SOURCE_AUTOFILL, SOURCE_PROCESS_TEXT}) + @Retention(RetentionPolicy.SOURCE) + public @interface Source {} + + /** + * Specifies that the operation was triggered by a paste from the clipboard (e.g. "Paste" or + * "Paste as plain text" action in the insertion/selection menu). + */ + public static final int SOURCE_CLIPBOARD = 0; + + /** + * Specifies that the operation was triggered from the soft keyboard (also known as input + * method editor or IME). See https://developer.android.com/guide/topics/text/image-keyboard + * for more info. + */ + public static final int SOURCE_INPUT_METHOD = 1; + + /** + * Specifies that the operation was triggered by the drag/drop framework. See + * https://developer.android.com/guide/topics/ui/drag-drop for more info. + */ + public static final int SOURCE_DRAG_AND_DROP = 2; + + /** + * Specifies that the operation was triggered by the autofill framework. See + * https://developer.android.com/guide/topics/text/autofill for more info. + */ + public static final int SOURCE_AUTOFILL = 3; + + /** + * Specifies that the operation was triggered by a result from a + * {@link android.content.Intent#ACTION_PROCESS_TEXT PROCESS_TEXT} action in the selection + * menu. + */ + public static final int SOURCE_PROCESS_TEXT = 4; + + /** + * Returns the symbolic name of the given source. + * + * @hide + */ + static String sourceToString(@Source int source) { + switch (source) { + case SOURCE_CLIPBOARD: return "SOURCE_CLIPBOARD"; + case SOURCE_INPUT_METHOD: return "SOURCE_INPUT_METHOD"; + case SOURCE_DRAG_AND_DROP: return "SOURCE_DRAG_AND_DROP"; + case SOURCE_AUTOFILL: return "SOURCE_AUTOFILL"; + case SOURCE_PROCESS_TEXT: return "SOURCE_PROCESS_TEXT"; + } + return String.valueOf(source); + } + + /** + * Flags to configure the insertion behavior. + * + * @hide + */ + @IntDef(flag = true, prefix = {"FLAG_"}, value = {FLAG_CONVERT_TO_PLAIN_TEXT}) + @Retention(RetentionPolicy.SOURCE) + public @interface Flags {} + + /** + * Flag requesting that the content should be converted to plain text prior to inserting. + */ + public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1 << 0; + + /** + * Returns the symbolic names of the set flags or {@code "0"} if no flags are set. + * + * @hide + */ + static String flagsToString(@Flags int flags) { + if ((flags & FLAG_CONVERT_TO_PLAIN_TEXT) != 0) { + return "FLAG_CONVERT_TO_PLAIN_TEXT"; + } + return String.valueOf(flags); + } + + /** + * The data to be inserted. + */ + @NonNull private final ClipData mClip; + + /** + * The source of the operation. See {@code SOURCE_} constants. + */ + private final @Source int mSource; + + /** + * Optional flags that control the insertion behavior. See {@code FLAG_} constants. + */ + private final @Flags int mFlags; + + /** + * Optional http/https URI for the content that may be provided by the IME. This is only + * populated if the source is {@link #SOURCE_INPUT_METHOD} and if a non-empty + * {@link android.view.inputmethod.InputContentInfo#getLinkUri linkUri} was passed by the + * IME. + */ + @Nullable + private final Uri mLinkUri; + + /** + * Optional additional metadata. If the source is {@link #SOURCE_INPUT_METHOD}, this will + * include the {@link android.view.inputmethod.InputConnection#commitContent opts} passed by + * the IME. + */ + @Nullable + private final Bundle mExtras; + + private Payload(Builder b) { + this.mClip = Objects.requireNonNull(b.mClip); + this.mSource = Preconditions.checkArgumentInRange(b.mSource, 0, SOURCE_PROCESS_TEXT, + "source"); + this.mFlags = Preconditions.checkFlagsArgument(b.mFlags, FLAG_CONVERT_TO_PLAIN_TEXT); + this.mLinkUri = b.mLinkUri; + this.mExtras = b.mExtras; + } + + @NonNull + @Override + public String toString() { + return "Payload{" + + "clip=" + mClip.getDescription() + + ", source=" + sourceToString(mSource) + + ", flags=" + flagsToString(mFlags) + + ", linkUri=" + mLinkUri + + ", extras=" + mExtras + + "}"; + } + + /** + * The data to be inserted. + */ + public @NonNull ClipData getClip() { + return mClip; + } + + /** + * The source of the operation. See {@code SOURCE_} constants. + */ + public @Source int getSource() { + return mSource; + } + + /** + * Optional flags that control the insertion behavior. See {@code FLAG_} constants. + */ + public @Flags int getFlags() { + return mFlags; + } + + /** + * Optional http/https URI for the content that may be provided by the IME. This is only + * populated if the source is {@link #SOURCE_INPUT_METHOD} and if a non-empty + * {@link android.view.inputmethod.InputContentInfo#getLinkUri linkUri} was passed by the + * IME. + */ + public @Nullable Uri getLinkUri() { + return mLinkUri; + } + + /** + * Optional additional metadata. If the source is {@link #SOURCE_INPUT_METHOD}, this will + * include the {@link android.view.inputmethod.InputConnection#commitContent opts} passed by + * the IME. + */ + public @Nullable Bundle getExtras() { + return mExtras; + } + + /** + * Builder for {@link Payload}. + */ + public static final class Builder { + @NonNull private final ClipData mClip; + private final @Source int mSource; + private @Flags int mFlags; + @Nullable private Uri mLinkUri; + @Nullable private Bundle mExtras; + + /** + * Creates a new builder. + * @param clip The data to insert. + * @param source The source of the operation. See {@code SOURCE_} constants. + */ + public Builder(@NonNull ClipData clip, @Source int source) { + mClip = clip; + mSource = source; + } + + /** + * Sets flags that control content insertion behavior. + * @param flags Optional flags to configure the insertion behavior. Use 0 for default + * behavior. See {@code FLAG_} constants. + * @return this builder + */ + @NonNull + public Builder setFlags(@Flags int flags) { + mFlags = flags; + return this; + } + + /** + * Sets the http/https URI for the content. See + * {@link android.view.inputmethod.InputContentInfo#getLinkUri} for more info. + * @param linkUri Optional http/https URI for the content. + * @return this builder + */ + @NonNull + public Builder setLinkUri(@Nullable Uri linkUri) { + mLinkUri = linkUri; + return this; + } + + /** + * Sets additional metadata. + * @param extras Optional bundle with additional metadata. + * @return this builder + */ + @NonNull + public Builder setExtras(@Nullable Bundle extras) { + mExtras = extras; + return this; + } + + /** + * @return A new {@link Payload} instance with the data from this builder. + */ + @NonNull + public Payload build() { + return new Payload(this); + } + } + } +} diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index a8ec9edf398d..78c71b856e2c 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1245,7 +1245,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * of the old SurfaceControl alive. */ private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot) { - final String name = "SurfaceView - " + viewRoot.getTitle().toString(); + final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]"; SurfaceControl.Builder builder = new SurfaceControl.Builder(mSurfaceSession) .setName(name) @@ -1270,7 +1270,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall .setBLASTLayer() .setCallsite("SurfaceView.updateSurface") .build(); - mBlastBufferQueue = new BLASTBufferQueue( + mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, true /* TODO */); } else { previousSurfaceControl = mSurfaceControl; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index e7e28ac98234..c430a4d57338 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3995,89 +3995,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * @hide - * - * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked - * out of the public fields to keep the undefined bits out of the developer's way. - * - * Flag to specify that the status bar is displayed in transient mode. - */ - public static final int STATUS_BAR_TRANSIENT = 0x04000000; - - /** - * @hide - * - * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked - * out of the public fields to keep the undefined bits out of the developer's way. - * - * Flag to specify that the navigation bar is displayed in transient mode. - */ - @UnsupportedAppUsage - public static final int NAVIGATION_BAR_TRANSIENT = 0x08000000; - - /** - * @hide - * - * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked - * out of the public fields to keep the undefined bits out of the developer's way. - * - * Flag to specify that the hidden status bar would like to be shown. - */ - public static final int STATUS_BAR_UNHIDE = 0x10000000; - - /** - * @hide - * - * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked - * out of the public fields to keep the undefined bits out of the developer's way. - * - * Flag to specify that the hidden navigation bar would like to be shown. - */ - public static final int NAVIGATION_BAR_UNHIDE = 0x20000000; - - /** - * @hide - * - * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked - * out of the public fields to keep the undefined bits out of the developer's way. - * - * Flag to specify that the status bar is displayed in translucent mode. - */ - public static final int STATUS_BAR_TRANSLUCENT = 0x40000000; - - /** - * @hide - * - * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked - * out of the public fields to keep the undefined bits out of the developer's way. - * - * Flag to specify that the navigation bar is displayed in translucent mode. - */ - public static final int NAVIGATION_BAR_TRANSLUCENT = 0x80000000; - - /** - * @hide - * - * Makes navigation bar transparent (but not the status bar). - */ - public static final int NAVIGATION_BAR_TRANSPARENT = 0x00008000; - - /** - * @hide - * - * Makes status bar transparent (but not the navigation bar). - */ - public static final int STATUS_BAR_TRANSPARENT = 0x00000008; - - /** - * @hide - * - * Makes both status bar and navigation bar transparent. - */ - public static final int SYSTEM_UI_TRANSPARENT = NAVIGATION_BAR_TRANSPARENT - | STATUS_BAR_TRANSPARENT; - - /** - * @hide */ public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x00003FF7; @@ -4302,31 +4219,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, name = "STATUS_BAR_DISABLE_RECENT"), @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_SEARCH, equals = STATUS_BAR_DISABLE_SEARCH, - name = "STATUS_BAR_DISABLE_SEARCH"), - @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSIENT, - equals = STATUS_BAR_TRANSIENT, - name = "STATUS_BAR_TRANSIENT"), - @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSIENT, - equals = NAVIGATION_BAR_TRANSIENT, - name = "NAVIGATION_BAR_TRANSIENT"), - @ViewDebug.FlagToString(mask = STATUS_BAR_UNHIDE, - equals = STATUS_BAR_UNHIDE, - name = "STATUS_BAR_UNHIDE"), - @ViewDebug.FlagToString(mask = NAVIGATION_BAR_UNHIDE, - equals = NAVIGATION_BAR_UNHIDE, - name = "NAVIGATION_BAR_UNHIDE"), - @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSLUCENT, - equals = STATUS_BAR_TRANSLUCENT, - name = "STATUS_BAR_TRANSLUCENT"), - @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSLUCENT, - equals = NAVIGATION_BAR_TRANSLUCENT, - name = "NAVIGATION_BAR_TRANSLUCENT"), - @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSPARENT, - equals = NAVIGATION_BAR_TRANSPARENT, - name = "NAVIGATION_BAR_TRANSPARENT"), - @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSPARENT, - equals = STATUS_BAR_TRANSPARENT, - name = "STATUS_BAR_TRANSPARENT") + name = "STATUS_BAR_DISABLE_SEARCH") }, formatToHexString = true) @SystemUiVisibility int mSystemUiVisibility; @@ -4355,14 +4248,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, STATUS_BAR_DISABLE_CLOCK, STATUS_BAR_DISABLE_RECENT, STATUS_BAR_DISABLE_SEARCH, - STATUS_BAR_TRANSIENT, - NAVIGATION_BAR_TRANSIENT, - STATUS_BAR_UNHIDE, - NAVIGATION_BAR_UNHIDE, - STATUS_BAR_TRANSLUCENT, - NAVIGATION_BAR_TRANSLUCENT, - NAVIGATION_BAR_TRANSPARENT, - STATUS_BAR_TRANSPARENT, }) @Retention(RetentionPolicy.SOURCE) public @interface SystemUiVisibility {} @@ -5358,6 +5243,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @InputSourceClass int mUnbufferedInputSource = InputDevice.SOURCE_CLASS_NONE; + @Nullable + private OnReceiveContentCallback<? extends View> mOnReceiveContentCallback; + /** * Simple constructor to use when creating a view from code. * @@ -9113,6 +9001,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns the callback used for handling insertion of content into this view. See + * {@link #setOnReceiveContentCallback} for more info. + * + * @return The callback for handling insertion of content. Returns null if no callback has been + * {@link #setOnReceiveContentCallback set}. + */ + @Nullable + public OnReceiveContentCallback<? extends View> getOnReceiveContentCallback() { + return mOnReceiveContentCallback; + } + + /** + * Sets the callback to handle insertion of content into this view. + * + * <p>Depending on the view, this callback may be invoked for scenarios such as content + * insertion from the IME, Autofill, etc. + * + * <p>The callback will only be invoked if the MIME type of the content is + * {@link OnReceiveContentCallback#getSupportedMimeTypes declared as supported} by the callback. + * If the content type is not supported by the callback, the default platform handling will be + * executed instead. + * + * @param callback The callback to use. This can be null to reset to the default behavior. + */ + public void setOnReceiveContentCallback( + @Nullable OnReceiveContentCallback<? extends View> callback) { + mOnReceiveContentCallback = callback; + } + + /** * Automatically fills the content of this view with the {@code value}. * * <p>Views support the Autofill Framework mainly by: diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4176e887c0e7..b653d219b90a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -33,6 +33,23 @@ import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewRootImplProto.ADDED; +import static android.view.ViewRootImplProto.APP_VISIBLE; +import static android.view.ViewRootImplProto.CUR_SCROLL_Y; +import static android.view.ViewRootImplProto.DISPLAY_ID; +import static android.view.ViewRootImplProto.HEIGHT; +import static android.view.ViewRootImplProto.IS_ANIMATING; +import static android.view.ViewRootImplProto.IS_DRAWING; +import static android.view.ViewRootImplProto.LAST_WINDOW_INSETS; +import static android.view.ViewRootImplProto.PENDING_DISPLAY_CUTOUT; +import static android.view.ViewRootImplProto.REMOVED; +import static android.view.ViewRootImplProto.SCROLL_Y; +import static android.view.ViewRootImplProto.SOFT_INPUT_MODE; +import static android.view.ViewRootImplProto.VIEW; +import static android.view.ViewRootImplProto.VISIBLE_RECT; +import static android.view.ViewRootImplProto.WIDTH; +import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES; +import static android.view.ViewRootImplProto.WIN_FRAME; import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER; import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; @@ -61,6 +78,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.IME_FOCUS_CONTROLLER; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.INSETS_CONTROLLER; import android.Manifest; import android.animation.LayoutTransition; @@ -127,6 +146,8 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; import android.util.TypedValue; +import android.util.imetracing.ImeTracing; +import android.util.proto.ProtoOutputStream; import android.view.InputDevice.InputSourceClass; import android.view.InsetsState.InternalInsetsType; import android.view.Surface.OutOfResourcesException; @@ -164,6 +185,7 @@ import android.window.ClientWindowFrames; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.os.IResultReceiver; import com.android.internal.os.SomeArgs; import com.android.internal.policy.DecorView; @@ -182,6 +204,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.CountDownLatch; @@ -339,8 +362,6 @@ public final class ViewRootImpl implements ViewParent, final int mTargetSdkVersion; - int mSeq; - @UnsupportedAppUsage View mView; @@ -648,7 +669,6 @@ public final class ViewRootImpl implements ViewParent, private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; static final class SystemUiVisibilityInfo { - int seq; int globalVisibility; int localValue; int localChanges; @@ -995,7 +1015,7 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); adjustLayoutParamsForCompatibility(mWindowAttributes); - res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes, + res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrames.frame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mDisplayCutout, inputChannel, @@ -1790,7 +1810,7 @@ public final class ViewRootImpl implements ViewParent, .setParent(getRenderSurfaceControl()) .setCallsite("ViewRootImpl.getBoundsLayer") .build(); - setBoundsLayerCrop(); + setBoundsLayerCrop(mTransaction); mTransaction.show(mBoundsLayer).apply(); } return mBoundsLayer; @@ -1806,7 +1826,7 @@ public final class ViewRootImpl implements ViewParent, Surface ret = null; if (mBlastBufferQueue == null) { - mBlastBufferQueue = new BLASTBufferQueue( + mBlastBufferQueue = new BLASTBufferQueue(mTag, mBlastSurfaceControl, width, height, mEnableTripleBuffering); // We only return the Surface the first time, as otherwise // it hasn't changed and there is no need to update. @@ -1818,25 +1838,41 @@ public final class ViewRootImpl implements ViewParent, return ret; } - private void setBoundsLayerCrop() { + private void setBoundsLayerCrop(Transaction t) { // mWinFrame is already adjusted for surface insets. So offset it and use it as // the cropping bounds. mTempBoundsRect.set(mWinFrame); mTempBoundsRect.offsetTo(mWindowAttributes.surfaceInsets.left, mWindowAttributes.surfaceInsets.top); - mTransaction.setWindowCrop(mBoundsLayer, mTempBoundsRect); + t.setWindowCrop(mBoundsLayer, mTempBoundsRect); } /** * Called after window layout to update the bounds surface. If the surface insets have changed * or the surface has resized, update the bounds surface. */ - private void updateBoundsLayer() { + private boolean updateBoundsLayer(SurfaceControl.Transaction t) { if (mBoundsLayer != null) { - setBoundsLayerCrop(); - mTransaction.deferTransactionUntil(mBoundsLayer, - getRenderSurfaceControl(), mSurface.getNextFrameNumber()) - .apply(); + setBoundsLayerCrop(t); + t.deferTransactionUntil(mBoundsLayer, getRenderSurfaceControl(), + mSurface.getNextFrameNumber()); + return true; + } + return false; + } + + private void prepareSurfaces(boolean sizeChanged) { + final SurfaceControl.Transaction t = mTransaction; + final SurfaceControl sc = getRenderSurfaceControl(); + if (!sc.isValid()) return; + + boolean applyTransaction = updateBoundsLayer(t); + if (sizeChanged) { + applyTransaction = true; + t.setBufferSize(sc, mSurfaceSize.x, mSurfaceSize.y); + } + if (applyTransaction) { + t.apply(); } } @@ -2927,7 +2963,16 @@ public final class ViewRootImpl implements ViewParent, } if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) { - updateBoundsLayer(); + // If the surface has been replaced, there's a chance the bounds layer is not parented + // to the new layer. When updating bounds layer, also reparent to the main VRI + // SurfaceControl to ensure it's correctly placed in the hierarchy. + // + // This needs to be done on the client side since WMS won't reparent the children to the + // new surface if it thinks the app is closing. WMS gets the signal that the app is + // stopping, but on the client side it doesn't get stopped since it's restarted quick + // enough. WMS doesn't want to keep around old children since they will leak when the + // client creates new children. + prepareSurfaces(surfaceSizeChanged); } final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); @@ -7390,7 +7435,7 @@ public final class ViewRootImpl implements ViewParent, frameNumber = mSurface.getNextFrameNumber(); } - int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params, + int relayoutResult = mWindowSession.relayout(mWindow, params, (int) (mView.getMeasuredWidth() * appScale + 0.5f), (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, @@ -7520,6 +7565,38 @@ public final class ViewRootImpl implements ViewParent, mView.debug(); } + /** + * Export the state of {@link ViewRootImpl} and other relevant classes into a protocol buffer + * output stream. + * + * @param proto Stream to write the state to + * @param fieldId FieldId of ViewRootImpl as defined in the parent message + */ + @GuardedBy("this") + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(VIEW, Objects.toString(mView)); + proto.write(DISPLAY_ID, mDisplay.getDisplayId()); + proto.write(APP_VISIBLE, mAppVisible); + proto.write(HEIGHT, mHeight); + proto.write(WIDTH, mWidth); + proto.write(IS_ANIMATING, mIsAnimating); + mVisRect.dumpDebug(proto, VISIBLE_RECT); + proto.write(IS_DRAWING, mIsDrawing); + proto.write(ADDED, mAdded); + mWinFrame.dumpDebug(proto, WIN_FRAME); + mPendingDisplayCutout.get().dumpDebug(proto, PENDING_DISPLAY_CUTOUT); + proto.write(LAST_WINDOW_INSETS, Objects.toString(mLastWindowInsets)); + proto.write(SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(mSoftInputMode)); + proto.write(SCROLL_Y, mScrollY); + proto.write(CUR_SCROLL_Y, mCurScrollY); + proto.write(REMOVED, mRemoved); + mWindowAttributes.dumpDebug(proto, WINDOW_ATTRIBUTES); + proto.end(token); + mInsetsController.dumpDebug(proto, INSETS_CONTROLLER); + mImeFocusController.dumpDebug(proto, IME_FOCUS_CONTROLLER); + } + public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { String innerPrefix = prefix + " "; writer.println(prefix + "ViewRoot:"); @@ -8460,17 +8537,6 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } - // TODO(118118435): Remove this after migration - public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, - int localValue, int localChanges) { - SystemUiVisibilityInfo args = new SystemUiVisibilityInfo(); - args.seq = seq; - args.globalVisibility = globalVisibility; - args.localValue = localValue; - args.localChanges = localChanges; - mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, args)); - } - public void dispatchCheckFocus() { if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) { // This will result in a call to checkFocus() below. @@ -9094,6 +9160,9 @@ public final class ViewRootImpl implements ViewParent, @Override public void showInsets(@InsetsType int types, boolean fromIme) { + if (fromIme) { + ImeTracing.getInstance().triggerDump(); + } final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.showInsets(types, fromIme); @@ -9102,6 +9171,9 @@ public final class ViewRootImpl implements ViewParent, @Override public void hideInsets(@InsetsType int types, boolean fromIme) { + if (fromIme) { + ImeTracing.getInstance().triggerDump(); + } final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.hideInsets(types, fromIme); @@ -9229,16 +9301,6 @@ public final class ViewRootImpl implements ViewParent, } @Override - public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, - int localValue, int localChanges) { - final ViewRootImpl viewAncestor = mViewAncestor.get(); - if (viewAncestor != null) { - viewAncestor.dispatchSystemUiVisibilityChanged(seq, globalVisibility, - localValue, localChanges); - } - } - - @Override public void dispatchWindowShown() { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 0d62da6bc8e3..e96e98b437a1 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -3994,4 +3994,15 @@ public interface WindowManager extends ViewManager { } } } + + /** + * Holds the WM lock for the specified amount of milliseconds. + * Intended for use by the tests that need to imitate lock contention. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) + default void holdLock(int durationMs) { + throw new UnsupportedOperationException(); + } } diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index f57ee65948c0..59e022645544 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -281,4 +281,13 @@ public final class WindowManagerImpl implements WindowManager { throw e.rethrowFromSystemServer(); } } + + @Override + public void holdLock(int durationMs) { + try { + WindowManagerGlobal.getWindowManagerService().holdLock(durationMs); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index b70cb015a275..dbd8184e57bb 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -129,7 +129,7 @@ public class WindowlessWindowManager implements IWindowSession { * IWindowSession implementation. */ @Override - public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, + public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, @@ -165,18 +165,18 @@ public class WindowlessWindowManager implements IWindowSession { * IWindowSession implementation. Currently this class doesn't need to support for multi-user. */ @Override - public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs, + public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, int userId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { - return addToDisplay(window, seq, attrs, viewVisibility, displayId, + return addToDisplay(window, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, outInsetsState, outActiveControls); } @Override - public int addToDisplayWithoutInputChannel(android.view.IWindow window, int seq, + public int addToDisplayWithoutInputChannel(android.view.IWindow window, android.view.WindowManager.LayoutParams attrs, int viewVisibility, int layerStackId, android.graphics.Rect outContentInsets, android.graphics.Rect outStableInsets, android.view.InsetsState insetsState) { @@ -223,7 +223,7 @@ public class WindowlessWindowManager implements IWindowSession { } @Override - public int relayout(IWindow window, int seq, WindowManager.LayoutParams inAttrs, + public int relayout(IWindow window, WindowManager.LayoutParams inAttrs, int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 44c754c21261..9ba886aba81a 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -2465,8 +2465,7 @@ public final class AutofillManager { * {@link #STATE_UNKNOWN_COMPAT_MODE} (beucase the session was finished when the URL bar * changed on compat mode), {@link #STATE_UNKNOWN_FAILED} (because the session was finished * when the service failed to fullfil the request, or {@link #STATE_DISABLED_BY_SERVICE} - * (because the autofill service or {@link #STATE_DISABLED_BY_SERVICE} (because the autofill - * service disabled further autofill requests for the activity). + * (because the autofill service disabled further autofill requests for the activity). * @param autofillableIds list of ids that could trigger autofill, use to not handle a new * session when they're entered. */ diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 05353651258e..16fd383fa7ff 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -643,54 +643,58 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // change should also get get rid of the "internalNotifyXXXX" methods above void notifyChildSessionStarted(int parentSessionId, int childSessionId, @NonNull ContentCaptureContext clientContext) { - sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) + mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) .setParentSessionId(parentSessionId).setClientContext(clientContext), - FORCE_FLUSH); + FORCE_FLUSH)); } void notifyChildSessionFinished(int parentSessionId, int childSessionId) { - sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) - .setParentSessionId(parentSessionId), FORCE_FLUSH); + mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) + .setParentSessionId(parentSessionId), FORCE_FLUSH)); } void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { - sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) - .setViewNode(node.mNode)); + mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) + .setViewNode(node.mNode))); } /** Public because is also used by ViewRootImpl */ public void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) { - sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id)); + mHandler.post(() -> sendEvent( + new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id))); } void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) { - sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED).setAutofillId(id) - .setText(text)); + mHandler.post(() -> sendEvent( + new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED) + .setAutofillId(id).setText(text))); } /** Public because is also used by ViewRootImpl */ public void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { - sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) - .setInsets(viewInsets)); + mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) + .setInsets(viewInsets))); } /** Public because is also used by ViewRootImpl */ public void notifyViewTreeEvent(int sessionId, boolean started) { final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED; - sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH); + mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH)); } void notifySessionResumed(int sessionId) { - sendEvent(new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH); + mHandler.post(() -> sendEvent( + new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH)); } void notifySessionPaused(int sessionId) { - sendEvent(new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH); + mHandler.post(() -> sendEvent( + new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH)); } void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { - sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) - .setClientContext(context)); + mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) + .setClientContext(context))); } @Override diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index eef27262c699..73636f81369f 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -16,7 +16,10 @@ package android.view.inputmethod; +import static android.view.OnReceiveContentCallback.Payload.SOURCE_INPUT_METHOD; + import android.annotation.CallSuper; +import android.content.ClipData; import android.content.Context; import android.content.res.TypedArray; import android.os.Bundle; @@ -34,6 +37,7 @@ import android.util.Log; import android.util.LogPrinter; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import android.view.OnReceiveContentCallback; import android.view.View; class ComposingText implements NoCopySpan { @@ -870,9 +874,41 @@ public class BaseInputConnection implements InputConnection { } /** - * The default implementation does nothing. + * Default implementation which invokes the target view's {@link OnReceiveContentCallback} if + * it is {@link View#setOnReceiveContentCallback set} and supports the MIME type of the given + * content; otherwise, simply returns false. */ public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { - return false; + @SuppressWarnings("unchecked") final OnReceiveContentCallback<View> receiver = + (OnReceiveContentCallback<View>) mTargetView.getOnReceiveContentCallback(); + if (receiver == null) { + if (DEBUG) { + Log.d(TAG, "Can't insert content from IME; no callback"); + } + return false; + } + if (!receiver.supports(mTargetView, inputContentInfo.getDescription())) { + if (DEBUG) { + Log.d(TAG, "Can't insert content from IME; callback doesn't support MIME type: " + + inputContentInfo.getDescription()); + } + return false; + } + if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { + try { + inputContentInfo.requestPermission(); + } catch (Exception e) { + Log.w(TAG, "Can't insert content from IME; requestPermission() failed", e); + return false; + } + } + final ClipData clip = new ClipData(inputContentInfo.getDescription(), + new ClipData.Item(inputContentInfo.getContentUri())); + final OnReceiveContentCallback.Payload payload = + new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_INPUT_METHOD) + .setLinkUri(inputContentInfo.getLinkUri()) + .setExtras(opts) + .build(); + return receiver.onReceiveContent(mTargetView, payload); } } diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index 104bc4347c29..7dbf69369996 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -17,6 +17,12 @@ package android.view.inputmethod; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.view.inputmethod.EditorInfoProto.FIELD_ID; +import static android.view.inputmethod.EditorInfoProto.IME_OPTIONS; +import static android.view.inputmethod.EditorInfoProto.INPUT_TYPE; +import static android.view.inputmethod.EditorInfoProto.PACKAGE_NAME; +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.NonNull; @@ -32,6 +38,7 @@ import android.text.InputType; import android.text.ParcelableSpan; import android.text.TextUtils; import android.util.Printer; +import android.util.proto.ProtoOutputStream; import android.view.View; import android.view.autofill.AutofillId; @@ -795,6 +802,26 @@ public class EditorInfo implements InputType, Parcelable { } /** + * Export the state of {@link EditorInfo} into a protocol buffer output stream. + * + * @param proto Stream to write the state to + * @param fieldId FieldId of ViewRootImpl as defined in the parent message + * @hide + */ + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(INPUT_TYPE, inputType); + proto.write(IME_OPTIONS, imeOptions); + proto.write(PRIVATE_IME_OPTIONS, privateImeOptions); + proto.write(PACKAGE_NAME, packageName); + proto.write(FIELD_ID, this.fieldId); + if (targetInputMethodUser != null) { + proto.write(TARGET_INPUT_METHOD_USER_ID, targetInputMethodUser.getIdentifier()); + } + proto.end(token); + } + + /** * Write debug output of this object. */ public void dump(Printer pw, String prefix) { diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index a3c95a916669..b8f04159faa9 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -18,6 +18,17 @@ package android.view.inputmethod; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; +import static android.util.imetracing.ImeTracing.PROTO_ARG; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.DISPLAY_ID; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.EDITOR_INFO; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.IME_INSETS_SOURCE_CONSUMER; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.INPUT_METHOD_MANAGER; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.VIEW_ROOT_IMPL; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientsProto.CLIENT; +import static android.view.inputmethod.InputMethodManagerProto.ACTIVE; +import static android.view.inputmethod.InputMethodManagerProto.CUR_ID; +import static android.view.inputmethod.InputMethodManagerProto.FULLSCREEN_MODE; +import static android.view.inputmethod.InputMethodManagerProto.SERVED_CONNECTING; import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION; import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION; @@ -62,6 +73,8 @@ import android.util.Pools.SimplePool; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.SparseArray; +import android.util.imetracing.ImeTracing; +import android.util.proto.ProtoOutputStream; import android.view.Display; import android.view.ImeFocusController; import android.view.ImeInsetsSourceConsumer; @@ -564,6 +577,7 @@ public final class InputMethodManager { @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags) { final View servedView; + ImeTracing.getInstance().triggerDump(); synchronized (mH) { mCurrentTextBoxAttribute = null; mCompletions = null; @@ -610,7 +624,8 @@ public final class InputMethodManager { @Override public void startInputAsyncOnWindowFocusGain(View focusedView, @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) { - final int startInputFlags = getStartInputFlags(focusedView, 0); + int startInputFlags = getStartInputFlags(focusedView, 0); + startInputFlags |= StartInputFlags.WINDOW_GAINED_FOCUS; final ImeFocusController controller = getFocusController(); if (controller == null) { @@ -1083,6 +1098,11 @@ public final class InputMethodManager { mH.obtainMessage(MSG_UPDATE_ACTIVITY_VIEW_TO_SCREEN_MATRIX, bindSequence, 0, matrixValues).sendToTarget(); } + + @Override + public void setImeTraceEnabled(boolean enabled) { + ImeTracing.getInstance().setEnabled(enabled); + } }; final InputConnection mDummyInputConnection = new BaseInputConnection(this, false); @@ -1651,6 +1671,7 @@ public final class InputMethodManager { * {@link #RESULT_HIDDEN}. */ public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) { + ImeTracing.getInstance().triggerDump(); // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); if (fallbackImm != null) { @@ -1756,6 +1777,7 @@ public final class InputMethodManager { */ public boolean hideSoftInputFromWindow(IBinder windowToken, int flags, ResultReceiver resultReceiver) { + ImeTracing.getInstance().triggerDump(); checkFocus(); synchronized (mH) { final View servedView = getServedViewLocked(); @@ -3107,6 +3129,10 @@ public final class InputMethodManager { } void doDump(FileDescriptor fd, PrintWriter fout, String[] args) { + if (processDump(fd, args)) { + return; + } + final Printer p = new PrintWriterPrinter(fout); p.println("Input method client state for " + this + ":"); @@ -3201,4 +3227,74 @@ public final class InputMethodManager { return sb.toString(); } + + /** + * Checks the args to see if a proto-based ime dump was requested and writes the client side + * ime dump to the given {@link FileDescriptor}. + * + * @return {@code true} if a proto-based ime dump was requested. + */ + private boolean processDump(final FileDescriptor fd, final String[] args) { + if (args == null) { + return false; + } + + for (String arg : args) { + if (arg.equals(PROTO_ARG)) { + final ProtoOutputStream proto = new ProtoOutputStream(fd); + dumpProto(proto); + proto.flush(); + return true; + } + } + return false; + } + + /** + * Write the proto dump for all displays associated with this client. + * + * @param proto The proto stream to which the dumps are written. + * @hide + */ + public static void dumpProto(ProtoOutputStream proto) { + for (int i = sInstanceMap.size() - 1; i >= 0; i--) { + InputMethodManager imm = sInstanceMap.valueAt(i); + imm.dumpDebug(proto); + } + } + + /** + * Write the proto dump of various client side components to the provided + * {@link ProtoOutputStream}. + * + * @param proto The proto stream to which the dumps are written. + * @hide + */ + @GuardedBy("mH") + public void dumpDebug(ProtoOutputStream proto) { + if (mCurMethod == null) { + return; + } + + final long clientDumpToken = proto.start(CLIENT); + proto.write(DISPLAY_ID, mDisplayId); + final long token = proto.start(INPUT_METHOD_MANAGER); + synchronized (mH) { + proto.write(CUR_ID, mCurId); + proto.write(FULLSCREEN_MODE, mFullscreenMode); + proto.write(ACTIVE, mActive); + proto.write(SERVED_CONNECTING, mServedConnecting); + proto.end(token); + if (mCurRootView != null) { + mCurRootView.dumpDebug(proto, VIEW_ROOT_IMPL); + } + if (mCurrentTextBoxAttribute != null) { + mCurrentTextBoxAttribute.dumpDebug(proto, EDITOR_INFO); + } + if (mImeInsetsConsumer != null) { + mImeInsetsConsumer.dumpDebug(proto, IME_INSETS_SOURCE_CONSUMER); + } + } + proto.end(clientDumpToken); + } } diff --git a/core/java/android/view/textservice/SpellCheckerSubtype.java b/core/java/android/view/textservice/SpellCheckerSubtype.java index 8224e0eb48da..12fe6263643b 100644 --- a/core/java/android/view/textservice/SpellCheckerSubtype.java +++ b/core/java/android/view/textservice/SpellCheckerSubtype.java @@ -18,6 +18,7 @@ package android.view.textservice; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Parcel; @@ -53,6 +54,7 @@ public final class SpellCheckerSubtype implements Parcelable { /** * @hide */ + @TestApi public static final int SUBTYPE_ID_NONE = 0; private static final String SUBTYPE_LANGUAGE_TAG_NONE = ""; diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java index acb35d63df9d..cd70a313db67 100644 --- a/core/java/android/view/textservice/TextServicesManager.java +++ b/core/java/android/view/textservice/TextServicesManager.java @@ -18,6 +18,7 @@ package android.view.textservice; import android.annotation.NonNull; import android.annotation.SystemService; +import android.annotation.TestApi; import android.annotation.UserIdInt; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -259,6 +260,7 @@ public final class TextServicesManager { * @hide */ @UnsupportedAppUsage + @TestApi public boolean isSpellCheckerEnabled() { try { return mService.isSpellCheckerEnabled(mUserId); diff --git a/core/java/android/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java index b04105ab1e75..28496c6f8a07 100644 --- a/core/java/android/webkit/PacProcessor.java +++ b/core/java/android/webkit/PacProcessor.java @@ -44,24 +44,17 @@ public interface PacProcessor { } /** - * Returns PacProcessor instance associated with the {@link Network}. - * The host resolution is done on this {@link Network}. + * Create a new PacProcessor instance. * - * <p> There can only be one {@link PacProcessor} instance at a time for each {@link Network}. - * This method will create a new instance if one did not already exist, or - * if the previous instance was released with {@link #releasePacProcessor}. - * - * <p> The {@link PacProcessor} instance needs to be released manually with - * {@link #releasePacProcessor} when the associated {@link Network} goes away. + * <p> The created instance needs to be released manually once it is no longer needed + * by calling {@link #releasePacProcessor} to prevent memory leaks. * - * @param network a {@link Network} which this {@link PacProcessor} - * will use for host/address resolution. - * If {@code null} this method is equivalent to {@link #getInstance}. - * @return {@link PacProcessor} instance for the specified network. + * <p> The created instance is not tied to any particular {@link Network}. + * To associate {@link PacProcessor} with a {@link Network} use {@link #setNetwork} method. */ @NonNull - static PacProcessor getInstanceForNetwork(@Nullable Network network) { - return WebViewFactory.getProvider().getPacProcessorForNetwork(network); + static PacProcessor createInstance() { + throw new UnsupportedOperationException("Not implemented"); } /** @@ -94,6 +87,18 @@ public interface PacProcessor { } /** + * Associate {@link PacProcessor} instance with the {@link Network}. + * Once this method returns host resolution is done on the set {@link Network}. + + * @param network a {@link Network} which this {@link PacProcessor} + * will use for host/address resolution. If {@code null} reset + * {@link PacProcessor} instance so it is not associated with any {@link Network}. + */ + default void setNetwork(@Nullable Network network) { + throw new UnsupportedOperationException("Not implemented"); + } + + /** * Returns a {@link Network} associated with this {@link PacProcessor}. * * @return an associated {@link Network} or {@code null} if a network is unspecified. diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 052bca57d77c..8eb1371505bf 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -704,18 +704,20 @@ public class WebView extends AbsoluteLayout return mProvider.restoreState(inState); } - /** - * Loads the given URL with the specified additional HTTP headers. + /** + * Loads the given URL with additional HTTP headers, specified as a map from + * name to value. Note that if this map contains any of the headers that are + * set by default by this WebView, such as those controlling caching, accept + * types or the User-Agent, their values may be overridden by this WebView's + * defaults. + * <p> + * Some older WebView implementations require {@code additionalHttpHeaders} + * to be mutable. * <p> * Also see compatibility note on {@link #evaluateJavascript}. * * @param url the URL of the resource to load - * @param additionalHttpHeaders the additional headers to be used in the - * HTTP request for this URL, specified as a map from name to - * value. Note that if this map contains any of the headers - * that are set by default by this WebView, such as those - * controlling caching, accept types or the User-Agent, their - * values may be overridden by this WebView's defaults. + * @param additionalHttpHeaders map with additional headers */ public void loadUrl(@NonNull String url, @NonNull Map<String, String> additionalHttpHeaders) { checkThread(); diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java index ce999cd5e235..3d6450632e06 100644 --- a/core/java/android/webkit/WebViewFactoryProvider.java +++ b/core/java/android/webkit/WebViewFactoryProvider.java @@ -17,7 +17,6 @@ package android.webkit; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; import android.content.Intent; @@ -186,8 +185,7 @@ public interface WebViewFactoryProvider { } /** - * Returns PacProcessor instance associated with the {@link Network}. - * The host resolution is done on this {@link Network}. + * Create a new PacProcessor instance. * * @param network a {@link Network} which needs to be associated * with the returned {@link PacProcessor}. @@ -195,7 +193,7 @@ public interface WebViewFactoryProvider { * @return the {@link PacProcessor} instance associated with {@link Network}. */ @NonNull - default PacProcessor getPacProcessorForNetwork(@Nullable Network network) { + default PacProcessor createPacProcessor() { throw new UnsupportedOperationException("Not implemented"); } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 60f8bb7ebe6c..fa195211fb54 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -16,6 +16,8 @@ package android.widget; +import static android.view.OnReceiveContentCallback.Payload.SOURCE_DRAG_AND_DROP; + import android.R; import android.animation.ValueAnimator; import android.annotation.IntDef; @@ -96,6 +98,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.OnReceiveContentCallback; import android.view.SubMenu; import android.view.View; import android.view.View.DragShadowBuilder; @@ -2851,9 +2854,11 @@ public class Editor { try { final int originalLength = mTextView.getText().length(); Selection.setSelection((Spannable) mTextView.getText(), offset); - ClipData clip = event.getClipData(); - mTextView.getRichContentReceiver().onReceive(mTextView, clip, - RichContentReceiver.SOURCE_DRAG_AND_DROP, 0); + final ClipData clip = event.getClipData(); + final OnReceiveContentCallback.Payload payload = + new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_DRAG_AND_DROP) + .build(); + mTextView.onReceiveContent(payload); if (dragDropIntoItself) { deleteSourceAfterLocalDrop(dragLocalState, offset, originalLength); } diff --git a/core/java/android/widget/RichContentReceiver.java b/core/java/android/widget/RichContentReceiver.java deleted file mode 100644 index 80a05622aa67..000000000000 --- a/core/java/android/widget/RichContentReceiver.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.widget; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.SuppressLint; -import android.content.ClipData; -import android.content.ClipDescription; -import android.view.View; -import android.view.inputmethod.InputConnection; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Set; - -/** - * Callback for apps to implement handling for insertion of rich content. "Rich content" here refers - * to both text and non-text content: plain text, styled text, HTML, images, videos, audio files, - * etc. - * - * <p>This callback can be attached to different types of UI components. For editable - * {@link TextView} components, implementations should typically wrap - * {@link TextView#DEFAULT_RICH_CONTENT_RECEIVER}. - * - * <p>Example implementation:<br> - * <pre class="prettyprint"> - * public class MyRichContentReceiver implements RichContentReceiver<TextView> { - * - * private static final Set<String> SUPPORTED_MIME_TYPES = Collections.unmodifiableSet( - * Set.of("text/*", "image/gif", "image/png", "image/jpg")); - * - * @NonNull - * @Override - * public Set<String> getSupportedMimeTypes() { - * return SUPPORTED_MIME_TYPES; - * } - * - * @Override - * public boolean onReceive(@NonNull TextView textView, @NonNull ClipData clip, - * int source, int flags) { - * if (clip.getDescription().hasMimeType("image/*")) { - * return receiveImage(textView, clip); - * } - * return TextView.DEFAULT_RICH_CONTENT_RECEIVER.onReceive(textView, clip, source); - * } - * - * private boolean receiveImage(@NonNull TextView textView, @NonNull ClipData clip) { - * // ... app-specific logic to handle the content URI in the clip ... - * } - * } - * </pre> - * - * @param <T> The type of {@link View} with which this receiver can be associated. - */ -@SuppressLint("CallbackMethodName") -public interface RichContentReceiver<T extends View> { - /** - * Specifies the UI through which content is being inserted. - * - * @hide - */ - @IntDef(prefix = {"SOURCE_"}, value = {SOURCE_CLIPBOARD, SOURCE_INPUT_METHOD, - SOURCE_DRAG_AND_DROP, SOURCE_AUTOFILL, SOURCE_PROCESS_TEXT}) - @Retention(RetentionPolicy.SOURCE) - @interface Source {} - - /** - * Specifies that the operation was triggered by a paste from the clipboard (e.g. "Paste" or - * "Paste as plain text" action in the insertion/selection menu). - */ - int SOURCE_CLIPBOARD = 0; - - /** - * Specifies that the operation was triggered from the soft keyboard (also known as input method - * editor or IME). See https://developer.android.com/guide/topics/text/image-keyboard for more - * info. - */ - int SOURCE_INPUT_METHOD = 1; - - /** - * Specifies that the operation was triggered by the drag/drop framework. See - * https://developer.android.com/guide/topics/ui/drag-drop for more info. - */ - int SOURCE_DRAG_AND_DROP = 2; - - /** - * Specifies that the operation was triggered by the autofill framework. See - * https://developer.android.com/guide/topics/text/autofill for more info. - */ - int SOURCE_AUTOFILL = 3; - - /** - * Specifies that the operation was triggered by a result from a - * {@link android.content.Intent#ACTION_PROCESS_TEXT PROCESS_TEXT} action in the selection menu. - */ - int SOURCE_PROCESS_TEXT = 4; - - /** - * Flags to configure the insertion behavior. - * - * @hide - */ - @IntDef(flag = true, prefix = {"FLAG_"}, value = {FLAG_CONVERT_TO_PLAIN_TEXT}) - @Retention(RetentionPolicy.SOURCE) - @interface Flags {} - - /** - * Flag for {@link #onReceive} requesting that the content should be converted to plain text - * prior to inserting. - */ - int FLAG_CONVERT_TO_PLAIN_TEXT = 1 << 0; - - /** - * Insert the given clip. - * - * <p>For editable {@link TextView} components, this function will be invoked for the - * following scenarios: - * <ol> - * <li>Paste from the clipboard (e.g. "Paste" or "Paste as plain text" action in the - * insertion/selection menu) - * <li>Content insertion from the keyboard ({@link InputConnection#commitContent}) - * <li>Drag and drop ({@link View#onDragEvent}) - * <li>Autofill, when the type for the field is - * {@link android.view.View.AutofillType#AUTOFILL_TYPE_RICH_CONTENT} - * </ol> - * - * <p>For text, if the view has a selection, the selection should be overwritten by the - * clip; if there's no selection, this method should insert the content at the current - * cursor position. - * - * <p>For rich content (e.g. an image), this function may insert the content inline, or it may - * add the content as an attachment (could potentially go into a completely separate view). - * - * <p>This function may be invoked with a clip whose MIME type is not in the list of supported - * types returned by {@link #getSupportedMimeTypes()}. This provides the opportunity to - * implement custom fallback logic if desired. - * - * @param view The view where the content insertion was requested. - * @param clip The clip to insert. - * @param source The trigger of the operation. - * @param flags Optional flags to configure the insertion behavior. Use 0 for default - * behavior. See {@code FLAG_} constants on this interface for other options. - * @return Returns true if the clip was inserted. - */ - boolean onReceive(@NonNull T view, @NonNull ClipData clip, @Source int source, int flags); - - /** - * Returns the MIME types that can be handled by this callback. - * - * <p>Different platform features (e.g. pasting from the clipboard, inserting stickers from the - * keyboard, etc) may use this function to conditionally alter their behavior. For example, the - * keyboard may choose to hide its UI for inserting GIFs if the input field that has focus has - * a {@link RichContentReceiver} set and the MIME types returned from this function don't - * include "image/gif". - * - * @return An immutable set with the MIME types supported by this callback. The returned - * MIME types may contain wildcards such as "text/*", "image/*", etc. - */ - @NonNull - Set<String> getSupportedMimeTypes(); - - /** - * Returns true if the MIME type of the given clip is {@link #getSupportedMimeTypes supported} - * by this receiver. - * - * @hide - */ - default boolean supports(@NonNull ClipDescription description) { - for (String supportedMimeType : getSupportedMimeTypes()) { - if (description.hasMimeType(supportedMimeType)) { - return true; - } - } - return false; - } - - /** - * Returns true if this receiver {@link #getSupportedMimeTypes supports} non-text content, such - * as images. - * - * @hide - */ - default boolean supportsNonTextContent() { - for (String supportedMimeType : getSupportedMimeTypes()) { - if (!supportedMimeType.startsWith("text/")) { - return true; - } - } - return false; - } - - /** - * Returns the symbolic name of the given source. - * - * @hide - */ - static String sourceToString(@Source int source) { - switch (source) { - case SOURCE_CLIPBOARD: return "SOURCE_CLIPBOARD"; - case SOURCE_INPUT_METHOD: return "SOURCE_INPUT_METHOD"; - case SOURCE_DRAG_AND_DROP: return "SOURCE_DRAG_AND_DROP"; - case SOURCE_AUTOFILL: return "SOURCE_AUTOFILL"; - case SOURCE_PROCESS_TEXT: return "SOURCE_PROCESS_TEXT"; - } - return String.valueOf(source); - } - - /** - * Returns the symbolic names of the set flags or {@code "0"} if no flags are set. - * - * @hide - */ - static String flagsToString(@Flags int flags) { - if ((flags & FLAG_CONVERT_TO_PLAIN_TEXT) != 0) { - return "FLAG_CONVERT_TO_PLAIN_TEXT"; - } - return String.valueOf(flags); - } -} diff --git a/core/java/android/widget/TEST_MAPPING b/core/java/android/widget/TEST_MAPPING index b5beac99d4e2..49c409368448 100644 --- a/core/java/android/widget/TEST_MAPPING +++ b/core/java/android/widget/TEST_MAPPING @@ -22,7 +22,7 @@ "name": "CtsAutoFillServiceTestCases", "options": [ { - "include-filter": "android.autofillservice.cts.LoginActivityTest" + "include-filter": "android.autofillservice.cts.dropdown.LoginActivityTest" }, { "exclude-annotation": "androidx.test.filters.FlakyTest" @@ -36,7 +36,7 @@ "name": "CtsAutoFillServiceTestCases", "options": [ { - "include-filter": "android.autofillservice.cts.CheckoutActivityTest" + "include-filter": "android.autofillservice.cts.dropdown.CheckoutActivityTest" }, { "exclude-annotation": "androidx.test.filters.FlakyTest" diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 6f14dfb89e6b..52a3f4145e7e 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -17,12 +17,15 @@ package android.widget; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.view.OnReceiveContentCallback.Payload.FLAG_CONVERT_TO_PLAIN_TEXT; +import static android.view.OnReceiveContentCallback.Payload.SOURCE_AUTOFILL; +import static android.view.OnReceiveContentCallback.Payload.SOURCE_CLIPBOARD; +import static android.view.OnReceiveContentCallback.Payload.SOURCE_PROCESS_TEXT; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; -import static android.widget.RichContentReceiver.SOURCE_PROCESS_TEXT; import android.R; import android.annotation.CallSuper; @@ -39,6 +42,7 @@ import android.annotation.RequiresPermission; import android.annotation.Size; import android.annotation.StringRes; import android.annotation.StyleRes; +import android.annotation.TestApi; import android.annotation.XmlRes; import android.app.Activity; import android.app.PendingIntent; @@ -149,6 +153,7 @@ import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.OnReceiveContentCallback; import android.view.PointerIcon; import android.view.View; import android.view.ViewConfiguration; @@ -426,7 +431,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * @hide */ - static final int PROCESS_TEXT_REQUEST_CODE = 100; + @TestApi + public static final int PROCESS_TEXT_REQUEST_CODE = 100; /** * Return code of {@link #doKeyDown}. @@ -735,6 +741,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mLocalesChanged = false; private int mTextSizeUnit = -1; + // True if force bold text feature is enabled. This feature makes all text bolder. + private boolean mForceBoldTextEnabled; + private Typeface mOriginalTypeface; + // True if setKeyListener() has been explicitly called private boolean mListenerChanged = false; // True if internationalized input should be used for numbers and date and time. @@ -882,12 +892,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * The default content insertion callback used by {@link TextView}. See - * {@link #setRichContentReceiver} for more info. + * {@link #setOnReceiveContentCallback} for more info. */ - public static final @NonNull RichContentReceiver<TextView> DEFAULT_RICH_CONTENT_RECEIVER = - TextViewRichContentReceiver.INSTANCE; - - private RichContentReceiver<TextView> mRichContentReceiver = DEFAULT_RICH_CONTENT_RECEIVER; + private static final TextViewOnReceiveContentCallback DEFAULT_ON_RECEIVE_CONTENT_CALLBACK = + new TextViewOnReceiveContentCallback(); private static final int DEVICE_PROVISIONED_UNKNOWN = 0; private static final int DEVICE_PROVISIONED_NO = 1; @@ -1645,6 +1653,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener attributes.mTypefaceIndex = MONOSPACE; } + mForceBoldTextEnabled = getContext().getResources().getConfiguration().forceBoldText + == Configuration.FORCE_BOLD_TEXT_YES; applyTextAppearance(attributes); if (isPassword) { @@ -2138,15 +2148,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * @hide */ + @TestApi @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (requestCode == PROCESS_TEXT_REQUEST_CODE) { if (resultCode == Activity.RESULT_OK && data != null) { CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); if (result != null) { if (isTextEditable()) { ClipData clip = ClipData.newPlainText("", result); - mRichContentReceiver.onReceive(this, clip, SOURCE_PROCESS_TEXT, 0); + OnReceiveContentCallback.Payload payload = + new OnReceiveContentCallback.Payload.Builder( + clip, SOURCE_PROCESS_TEXT) + .build(); + onReceiveContent(payload); if (mEditor != null) { mEditor.refreshTextActionMode(); } @@ -4267,6 +4282,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener invalidate(); } } + if (newConfig.forceBoldText == Configuration.FORCE_BOLD_TEXT_YES) { + mForceBoldTextEnabled = true; + setTypeface(getTypeface()); + } else if (newConfig.forceBoldText == Configuration.FORCE_BOLD_TEXT_NO + || newConfig.forceBoldText == Configuration.FORCE_BOLD_TEXT_UNDEFINED) { + mForceBoldTextEnabled = false; + setTypeface(getTypeface()); + } } /** @@ -4418,6 +4441,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_textStyle */ public void setTypeface(@Nullable Typeface tf) { + mOriginalTypeface = tf; + if (mForceBoldTextEnabled) { + int newWeight = tf != null ? tf.getWeight() + 300 : 400; + newWeight = Math.min(newWeight, 1000); + int typefaceStyle = tf != null ? tf.getStyle() : 0; + boolean italic = (typefaceStyle & Typeface.ITALIC) != 0; + tf = Typeface.create(tf, newWeight, italic); + } if (mTextPaint.getTypeface() != tf) { mTextPaint.setTypeface(tf); @@ -4441,7 +4472,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @InspectableProperty public Typeface getTypeface() { - return mTextPaint.getTypeface(); + return mOriginalTypeface; } /** @@ -8722,9 +8753,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); outAttrs.setInitialSurroundingText(mText); - int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; - if (targetSdkVersion > Build.VERSION_CODES.R) { - outAttrs.contentMimeTypes = mRichContentReceiver.getSupportedMimeTypes() + // If a custom `OnReceiveContentCallback` is set, pass its supported MIME types. + OnReceiveContentCallback<TextView> receiver = getOnReceiveContentCallback(); + if (receiver != null) { + outAttrs.contentMimeTypes = receiver.getSupportedMimeTypes(this) .toArray(new String[0]); } return ic; @@ -11827,7 +11859,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this); return; } - ClipData clip; + final ClipData clip; if (value.isRichContent()) { clip = value.getRichContentValue(); } else if (value.isText()) { @@ -11837,22 +11869,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener + " cannot be autofilled into " + this); return; } - mRichContentReceiver.onReceive(this, clip, RichContentReceiver.SOURCE_AUTOFILL, 0); + final OnReceiveContentCallback.Payload payload = + new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_AUTOFILL).build(); + onReceiveContent(payload); } @Override public @AutofillType int getAutofillType() { - if (!isTextEditable()) { - return AUTOFILL_TYPE_NONE; - } - final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; - if (targetSdkVersion <= Build.VERSION_CODES.R) { - return AUTOFILL_TYPE_TEXT; - } - // TODO(b/147301047): Update autofill framework code to check the target SDK of the autofill - // provider and force the type AUTOFILL_TYPE_TEXT for providers that target older SDKs. - return mRichContentReceiver.supportsNonTextContent() ? AUTOFILL_TYPE_RICH_CONTENT - : AUTOFILL_TYPE_TEXT; + return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; } /** @@ -12913,8 +12937,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (clip == null) { return; } - int flags = withFormatting ? 0 : RichContentReceiver.FLAG_CONVERT_TO_PLAIN_TEXT; - mRichContentReceiver.onReceive(this, clip, RichContentReceiver.SOURCE_CLIPBOARD, flags); + final OnReceiveContentCallback.Payload payload = + new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_CLIPBOARD) + .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT) + .build(); + onReceiveContent(payload); sLastCutCopyOrTextChangedTime = 0; } @@ -13697,43 +13724,58 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Returns the callback that handles insertion of content into this view (e.g. pasting from - * the clipboard). See {@link #setRichContentReceiver} for more info. + * Returns the callback used for handling insertion of content into this view. See + * {@link #setOnReceiveContentCallback} for more info. * - * @return The callback that this view is using to handle insertion of content. Returns - * {@link #DEFAULT_RICH_CONTENT_RECEIVER} if no custom callback has been - * {@link #setRichContentReceiver set}. + * @return The callback for handling insertion of content. Returns null if no callback has been + * {@link #setOnReceiveContentCallback set}. */ - @NonNull - public RichContentReceiver<TextView> getRichContentReceiver() { - return mRichContentReceiver; + @SuppressWarnings("unchecked") + @Nullable + @Override + public OnReceiveContentCallback<TextView> getOnReceiveContentCallback() { + return (OnReceiveContentCallback<TextView>) super.getOnReceiveContentCallback(); } /** * Sets the callback to handle insertion of content into this view. * - * <p>"Content" and "rich content" here refers to both text and non-text: plain text, styled - * text, HTML, images, videos, audio files, etc. - * - * <p>The callback configured here should typically wrap {@link #DEFAULT_RICH_CONTENT_RECEIVER} - * to provide consistent behavior for text content. - * * <p>This callback will be invoked for the following scenarios: * <ol> * <li>Paste from the clipboard (e.g. "Paste" or "Paste as plain text" action in the * insertion/selection menu) - * <li>Content insertion from the keyboard ({@link InputConnection#commitContent}) - * <li>Drag and drop ({@link View#onDragEvent}) - * <li>Autofill, when the type for the field is - * {@link android.view.View.AutofillType#AUTOFILL_TYPE_RICH_CONTENT} + * <li>Content insertion from the keyboard (from {@link InputConnection#commitContent}) + * <li>Drag and drop (drop events from {@link #onDragEvent(DragEvent)}) + * <li>Autofill (from {@link #autofill(AutofillValue)}) + * <li>{@link Intent#ACTION_PROCESS_TEXT} replacement * </ol> * - * @param receiver The callback to use. This can be {@link #DEFAULT_RICH_CONTENT_RECEIVER} to - * reset to the default behavior. + * <p>The callback will only be invoked if the MIME type of the content is + * {@link OnReceiveContentCallback#getSupportedMimeTypes declared as supported} by the callback. + * If the content type is not supported by the callback, the default platform handling will be + * executed instead. + * + * @param callback The callback to use. This can be null to reset to the default behavior. */ - public void setRichContentReceiver(@NonNull RichContentReceiver<TextView> receiver) { - mRichContentReceiver = Objects.requireNonNull(receiver, - "RichContentReceiver should not be null."); + @Override + public void setOnReceiveContentCallback( + @Nullable OnReceiveContentCallback<? extends View> callback) { + super.setOnReceiveContentCallback(callback); + } + + /** + * Handles the request to insert content using the configured callback or the default callback. + * + * @hide + */ + void onReceiveContent(@NonNull OnReceiveContentCallback.Payload payload) { + OnReceiveContentCallback<TextView> receiver = getOnReceiveContentCallback(); + ClipDescription description = payload.getClip().getDescription(); + if (receiver != null && receiver.supports(this, description)) { + receiver.onReceiveContent(this, payload); + } else { + DEFAULT_ON_RECEIVE_CONTENT_CALLBACK.onReceiveContent(this, payload); + } } private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { diff --git a/core/java/android/widget/TextViewRichContentReceiver.java b/core/java/android/widget/TextViewOnReceiveContentCallback.java index 4f2d95466997..35618cb3d2a5 100644 --- a/core/java/android/widget/TextViewRichContentReceiver.java +++ b/core/java/android/widget/TextViewOnReceiveContentCallback.java @@ -16,7 +16,12 @@ package android.widget; +import static android.view.OnReceiveContentCallback.Payload.FLAG_CONVERT_TO_PLAIN_TEXT; +import static android.view.OnReceiveContentCallback.Payload.SOURCE_AUTOFILL; +import static android.view.OnReceiveContentCallback.Payload.SOURCE_DRAG_AND_DROP; + import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.content.ClipData; import android.content.Context; import android.text.Editable; @@ -24,54 +29,45 @@ import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.util.Log; +import android.view.OnReceiveContentCallback; +import android.view.OnReceiveContentCallback.Payload.Flags; +import android.view.OnReceiveContentCallback.Payload.Source; import java.util.Collections; import java.util.Set; /** - * Default implementation of {@link RichContentReceiver} for editable {@link TextView} components. - * This class handles insertion of text (plain text, styled text, HTML, etc) but not images or other - * rich content. Typically this class will be used as a delegate by custom implementations of - * {@link RichContentReceiver}, to provide consistent behavior for insertion of text while - * implementing custom behavior for insertion of other content (images, etc). See - * {@link TextView#DEFAULT_RICH_CONTENT_RECEIVER}. - * - * @hide + * Default implementation of {@link android.view.OnReceiveContentCallback} for editable + * {@link TextView} components. This class handles insertion of text (plain text, styled text, HTML, + * etc) but not images or other content. This class can be used as a base class for an + * implementation of {@link android.view.OnReceiveContentCallback} for a {@link TextView}, to + * provide consistent behavior for insertion of text. */ -final class TextViewRichContentReceiver implements RichContentReceiver<TextView> { - static final TextViewRichContentReceiver INSTANCE = new TextViewRichContentReceiver(); - - private static final String LOG_TAG = "RichContentReceiver"; +public class TextViewOnReceiveContentCallback implements OnReceiveContentCallback<TextView> { + private static final String LOG_TAG = "OnReceiveContent"; private static final Set<String> MIME_TYPES_ALL_TEXT = Collections.singleton("text/*"); + @SuppressLint("CallbackMethodName") + @NonNull @Override - public Set<String> getSupportedMimeTypes() { + public Set<String> getSupportedMimeTypes(@NonNull TextView view) { return MIME_TYPES_ALL_TEXT; } @Override - public boolean onReceive(@NonNull TextView textView, @NonNull ClipData clip, - @Source int source, @Flags int flags) { + public boolean onReceiveContent(@NonNull TextView view, @NonNull Payload payload) { if (Log.isLoggable(LOG_TAG, Log.DEBUG)) { - StringBuilder sb = new StringBuilder("onReceive: clip="); - if (clip.getDescription() == null) { - sb.append("null"); - } else { - clip.getDescription().toShortStringTypesOnly(sb); - } - sb.append(", source=").append(RichContentReceiver.sourceToString(source)); - sb.append(", flags=").append(RichContentReceiver.flagsToString(flags)); - Log.d(LOG_TAG, sb.toString()); + Log.d(LOG_TAG, "onReceive:" + payload); } + ClipData clip = payload.getClip(); + @Source int source = payload.getSource(); + @Flags int flags = payload.getFlags(); if (source == SOURCE_AUTOFILL) { - return onReceiveForAutofill(textView, clip, flags); + return onReceiveForAutofill(view, clip, flags); } if (source == SOURCE_DRAG_AND_DROP) { - return onReceiveForDragAndDrop(textView, clip, flags); - } - if (source == SOURCE_INPUT_METHOD && !supports(clip.getDescription())) { - return false; + return onReceiveForDragAndDrop(view, clip, flags); } // The code here follows the original paste logic from TextView: @@ -79,8 +75,8 @@ final class TextViewRichContentReceiver implements RichContentReceiver<TextView> // In particular, multiple items within the given ClipData will trigger separate calls to // replace/insert. This is to preserve the original behavior with respect to TextWatcher // notifications fired from SpannableStringBuilder when replace/insert is called. - final Editable editable = (Editable) textView.getText(); - final Context context = textView.getContext(); + final Editable editable = (Editable) view.getText(); + final Context context = view.getContext(); boolean didFirst = false; for (int i = 0; i < clip.getItemCount(); i++) { CharSequence itemText; @@ -100,7 +96,7 @@ final class TextViewRichContentReceiver implements RichContentReceiver<TextView> } } } - return didFirst; + return true; } private static void replaceSelection(@NonNull Editable editable, @@ -128,7 +124,7 @@ final class TextViewRichContentReceiver implements RichContentReceiver<TextView> @NonNull ClipData clip, @Flags int flags) { final CharSequence text = coerceToText(clip, textView.getContext(), flags); if (text.length() == 0) { - return false; + return true; } replaceSelection((Editable) textView.getText(), text); return true; diff --git a/core/java/android/window/ITransitionPlayer.aidl b/core/java/android/window/ITransitionPlayer.aidl new file mode 100644 index 000000000000..a8a29b26a148 --- /dev/null +++ b/core/java/android/window/ITransitionPlayer.aidl @@ -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 android.window; + +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.WindowContainerTransaction; + +/** + * Implemented by WMShell to initiate and play transition animations. + * The flow (with {@link IWindowOrganizerController}) looks like this: + * <p><ol> + * <li>Core starts an activity and calls {@link #requestStartTransition} + * <li>This TransitionPlayer impl does whatever, then calls + * {@link IWindowOrganizerController#startTransition} to tell Core to formally start (until + * this happens, Core will collect changes on the transition, but won't consider it ready to + * animate). + * <li>Once all collected changes on the transition have finished drawing, Core will then call + * {@link #onTransitionReady} here to delegate the actual animation. + * <li>Once this TransitionPlayer impl finishes animating, it notifies Core via + * {@link IWindowOrganizerController#finishTransition}. At this point, ITransitionPlayer's + * responsibilities end. + * </ul> + * + * {@hide} + */ +oneway interface ITransitionPlayer { + + /** + * Called when all participants of a transition are ready to animate. This is in response to + * {@link IWindowOrganizerController#startTransition}. + * + * @param transitionToken An identifying token for the transition that is now ready to animate. + * @param info A collection of all the changes encapsulated by this transition. + * @param t A surface transaction containing the surface state prior to animating. + */ + void onTransitionReady(in IBinder transitionToken, in TransitionInfo info, + in SurfaceControl.Transaction t); + + /** + * Called when something in WMCore requires a transition to play -- for example when an Activity + * is started in a new Task. + * + * @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}. + */ + void requestStartTransition(int type, in IBinder transitionToken); +} diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl index 7e9c783c83c6..0cd9b366bf5c 100644 --- a/core/java/android/window/IWindowOrganizerController.aidl +++ b/core/java/android/window/IWindowOrganizerController.aidl @@ -18,8 +18,10 @@ package android.window; import android.view.SurfaceControl; +import android.os.IBinder; import android.window.IDisplayAreaOrganizerController; import android.window.ITaskOrganizerController; +import android.window.ITransitionPlayer; import android.window.IWindowContainerTransactionCallback; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -45,6 +47,30 @@ interface IWindowOrganizerController { int applySyncTransaction(in WindowContainerTransaction t, in IWindowContainerTransactionCallback callback); + /** + * Starts a transition. + * @param type The transition type. + * @param transitionToken A token associated with the transition to start. If null, a new + * transition will be created of the provided type. + * @param t Operations that are part of the transition. + * @return a token representing the transition. This will just be transitionToken if it was + * non-null. + */ + IBinder startTransition(int type, in @nullable IBinder transitionToken, + in @nullable WindowContainerTransaction t); + + /** + * Finishes a transition. This must be called for all created transitions. + * @param transitionToken Which transition to finish + * @param t Changes to make before finishing but in the same SF Transaction. Can be null. + * @param callback Called when t is finished applying. + * @return An ID for the sync operation (see {@link #applySyncTransaction}. This will be + * negative if no sync transaction was attached (null t or callback) + */ + int finishTransition(in IBinder transitionToken, + in @nullable WindowContainerTransaction t, + in IWindowContainerTransactionCallback callback); + /** @return An interface enabling the management of task organizers. */ ITaskOrganizerController getTaskOrganizerController(); @@ -61,4 +87,10 @@ interface IWindowOrganizerController { * @return true if the screenshot was successful, false otherwise. */ boolean takeScreenshot(in WindowContainerToken token, out SurfaceControl outSurfaceControl); + + /** + * Registers a transition player with Core. There is only one of these at a time and calling + * this will replace the existing one if set. + */ + void registerTransitionPlayer(in ITransitionPlayer player); } diff --git a/core/java/android/window/TransitionInfo.aidl b/core/java/android/window/TransitionInfo.aidl new file mode 100644 index 000000000000..6c33e9737f6a --- /dev/null +++ b/core/java/android/window/TransitionInfo.aidl @@ -0,0 +1,20 @@ +/* + * 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.window; + +parcelable TransitionInfo; +parcelable TransitionInfo.Change; diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java new file mode 100644 index 000000000000..34d1d4e8699d --- /dev/null +++ b/core/java/android/window/TransitionInfo.java @@ -0,0 +1,290 @@ +/* + * 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.window; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.SurfaceControl; +import android.view.WindowManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * Used to communicate information about what is changing during a transition to a TransitionPlayer. + * @hide + */ +public final class TransitionInfo implements Parcelable { + + /** No transition mode. This is a placeholder, don't use this as an actual mode. */ + public static final int TRANSIT_NONE = 0; + + /** The container didn't exist before but will exist and be visible after. */ + public static final int TRANSIT_OPEN = 1; + + /** The container existed and was visible before but won't exist after. */ + public static final int TRANSIT_CLOSE = 2; + + /** The container existed before but was invisible and will be visible after. */ + public static final int TRANSIT_SHOW = 3; + + /** The container is going from visible to invisible but it will still exist after. */ + public static final int TRANSIT_HIDE = 4; + + /** The container exists and is visible before and after but it changes. */ + public static final int TRANSIT_CHANGE = 5; + + /** @hide */ + @IntDef(prefix = { "TRANSIT_" }, value = { + TRANSIT_NONE, + TRANSIT_OPEN, + TRANSIT_CLOSE, + TRANSIT_SHOW, + TRANSIT_HIDE, + TRANSIT_CHANGE + }) + public @interface TransitionMode {} + + private final @WindowManager.TransitionType int mType; + private final ArrayList<Change> mChanges = new ArrayList<>(); + + /** @hide */ + public TransitionInfo(@WindowManager.TransitionType int type) { + mType = type; + } + + private TransitionInfo(Parcel in) { + mType = in.readInt(); + in.readList(mChanges, null /* classLoader */); + } + + @Override + /** @hide */ + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeList(mChanges); + } + + @NonNull + public static final Creator<TransitionInfo> CREATOR = + new Creator<TransitionInfo>() { + @Override + public TransitionInfo createFromParcel(Parcel in) { + return new TransitionInfo(in); + } + + @Override + public TransitionInfo[] newArray(int size) { + return new TransitionInfo[size]; + } + }; + + @Override + /** @hide */ + public int describeContents() { + return 0; + } + + public int getType() { + return mType; + } + + @NonNull + public List<Change> getChanges() { + return mChanges; + } + + /** + * @return the Change that a window is undergoing or {@code null} if not directly + * represented. + */ + @Nullable + public Change getChange(@NonNull WindowContainerToken token) { + for (int i = mChanges.size() - 1; i >= 0; --i) { + if (mChanges.get(i).mContainer == token) { + return mChanges.get(i); + } + } + return null; + } + + /** + * Add a {@link Change} to this transition. + */ + public void addChange(@NonNull Change change) { + mChanges.add(change); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{t=" + mType + " c=["); + for (int i = 0; i < mChanges.size(); ++i) { + if (i > 0) { + sb.append(','); + } + sb.append(mChanges.get(i)); + } + sb.append("]}"); + return sb.toString(); + } + + /** Converts a transition mode/action to its string representation. */ + @NonNull + public static String modeToString(@TransitionMode int mode) { + switch(mode) { + case TRANSIT_NONE: return "NONE"; + case TRANSIT_OPEN: return "OPEN"; + case TRANSIT_CLOSE: return "CLOSE"; + case TRANSIT_SHOW: return "SHOW"; + case TRANSIT_HIDE: return "HIDE"; + case TRANSIT_CHANGE: return "CHANGE"; + default: return "<unknown:" + mode + ">"; + } + } + + /** Represents the change a WindowContainer undergoes during a transition */ + public static final class Change implements Parcelable { + private final WindowContainerToken mContainer; + private WindowContainerToken mParent; + private final SurfaceControl mLeash; + private int mMode = TRANSIT_NONE; + private final Rect mStartBounds = new Rect(); + private final Rect mEndBounds = new Rect(); + + public Change(@NonNull WindowContainerToken container, @NonNull SurfaceControl leash) { + mContainer = container; + mLeash = leash; + } + + private Change(Parcel in) { + mContainer = WindowContainerToken.CREATOR.createFromParcel(in); + mParent = in.readParcelable(WindowContainerToken.class.getClassLoader()); + mLeash = new SurfaceControl(); + mLeash.readFromParcel(in); + mMode = in.readInt(); + mStartBounds.readFromParcel(in); + mEndBounds.readFromParcel(in); + } + + /** Sets the parent of this change's container. The parent must be a participant or null. */ + public void setParent(@Nullable WindowContainerToken parent) { + mParent = parent; + } + + /** Sets the transition mode for this change */ + public void setMode(@TransitionMode int mode) { + mMode = mode; + } + + /** Sets the bounds this container occupied before the change */ + public void setStartBounds(@Nullable Rect rect) { + mStartBounds.set(rect); + } + + /** Sets the bounds this container will occupy after the change */ + public void setEndBounds(@Nullable Rect rect) { + mEndBounds.set(rect); + } + + /** @return the container that is changing */ + @NonNull + public WindowContainerToken getContainer() { + return mContainer; + } + + /** + * @return the parent of the changing container. This is the parent within the participants, + * not necessarily the actual parent. + */ + @Nullable + public WindowContainerToken getParent() { + return mParent; + } + + /** @return which action this change represents. */ + public @TransitionMode int getMode() { + return mMode; + } + + /** + * @return the bounds of the container before the change. It may be empty if the container + * is coming into existence. + */ + @NonNull + public Rect getStartBounds() { + return mStartBounds; + } + + /** + * @return the bounds of the container after the change. It may be empty if the container + * is disappearing. + */ + @NonNull + public Rect getEndBounds() { + return mEndBounds; + } + + /** @return the leash or surface to animate for this container */ + @NonNull + public SurfaceControl getLeash() { + return mLeash; + } + + @Override + /** @hide */ + public void writeToParcel(@NonNull Parcel dest, int flags) { + mContainer.writeToParcel(dest, flags); + dest.writeParcelable(mParent, 0); + mLeash.writeToParcel(dest, flags); + dest.writeInt(mMode); + mStartBounds.writeToParcel(dest, flags); + mEndBounds.writeToParcel(dest, flags); + } + + @NonNull + public static final Creator<Change> CREATOR = + new Creator<Change>() { + @Override + public Change createFromParcel(Parcel in) { + return new Change(in); + } + + @Override + public Change[] newArray(int size) { + return new Change[size]; + } + }; + + @Override + /** @hide */ + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "{" + mContainer + "(" + mParent + ") leash=" + mLeash + + " m=" + modeToString(mMode) + " sb=" + mStartBounds + + " eb=" + mEndBounds + "}"; + } + } +} diff --git a/core/java/android/window/WindowContainerToken.java b/core/java/android/window/WindowContainerToken.java index c92ccae66ff8..96e8b44d13cc 100644 --- a/core/java/android/window/WindowContainerToken.java +++ b/core/java/android/window/WindowContainerToken.java @@ -78,6 +78,11 @@ public final class WindowContainerToken implements Parcelable { } @Override + public String toString() { + return "WCT{" + mRealToken + "}"; + } + + @Override public boolean equals(Object obj) { if (!(obj instanceof WindowContainerToken)) { return false; diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java index 97a97d9984f9..5ac19fa685d7 100644 --- a/core/java/android/window/WindowOrganizer.java +++ b/core/java/android/window/WindowOrganizer.java @@ -19,8 +19,10 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.ActivityTaskManager; +import android.os.IBinder; import android.os.RemoteException; import android.util.Singleton; import android.view.SurfaceControl; @@ -66,6 +68,48 @@ public class WindowOrganizer { } /** + * Start a transition. + * @param type The type of the transition. This is ignored if a transitionToken is provided. + * @param transitionToken An existing transition to start. If null, a new transition is created. + * @param t The set of window operations that are part of this transition. + * @return A token identifying the transition. This will be the same as transitionToken if it + * was provided. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + @NonNull + public IBinder startTransition(int type, @Nullable IBinder transitionToken, + @Nullable WindowContainerTransaction t) { + try { + return getWindowOrganizerController().startTransition(type, transitionToken, t); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Finishes a running transition. + * @param transitionToken The transition to finish. Can't be null. + * @param t A set of window operations to apply before finishing. + * @param callback A sync callback (if provided). See {@link #applySyncTransaction}. + * @return An ID for the sync operation if performed. See {@link #applySyncTransaction}. + * + * @hide + */ + @SuppressLint("ExecutorRegistration") + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public int finishTransition(@NonNull IBinder transitionToken, + @Nullable WindowContainerTransaction t, + @Nullable WindowContainerTransactionCallback callback) { + try { + return getWindowOrganizerController().finishTransition(transitionToken, t, + callback != null ? callback.mInterface : null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Take a screenshot for a specified Window * @param token The token for the WindowContainer that should get a screenshot taken. * @return A SurfaceControl where the screenshot will be attached, or null if failed. @@ -87,6 +131,19 @@ public class WindowOrganizer { } } + /** + * Register an ITransitionPlayer to handle transition animations. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void registerTransitionPlayer(@Nullable ITransitionPlayer player) { + try { + getWindowOrganizerController().registerTransitionPlayer(player); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) IWindowOrganizerController getWindowOrganizerController() { return IWindowOrganizerControllerSingleton.get(); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 944f2ecdd647..61a625e40dcd 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -1771,7 +1771,7 @@ public class ChooserActivity extends ResolverActivity implements case ChooserListAdapter.TARGET_CALLER: case ChooserListAdapter.TARGET_STANDARD: cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET; - value -= currentListAdapter.getSelectableServiceTargetCount(); + value -= currentListAdapter.getSurfacedTargetInfo().size(); numCallerProvided = currentListAdapter.getCallerTargetCount(); getChooserActivityLogger().logShareTargetSelected( SELECTION_TYPE_APP, diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index 37f68233db53..3bcba75ec163 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -224,6 +224,8 @@ public final class InputMethodDebug { return "HIDE_DOCKED_STACK_ATTACHED"; case SoftInputShowHideReason.HIDE_RECENTS_ANIMATION: return "HIDE_RECENTS_ANIMATION"; + case SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR: + return "HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR"; default: return "Unknown=" + reason; } diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index 4b968b45f122..f46626be48a8 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -47,7 +47,8 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME, SoftInputShowHideReason.HIDE_DOCKED_STACK_ATTACHED, SoftInputShowHideReason.HIDE_RECENTS_ANIMATION, - SoftInputShowHideReason.HIDE_BUBBLES}) + SoftInputShowHideReason.HIDE_BUBBLES, + SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR}) public @interface SoftInputShowHideReason { /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */ int SHOW_SOFT_INPUT = 0; @@ -147,4 +148,17 @@ public @interface SoftInputShowHideReason { * switching, or collapsing Bubbles. */ int HIDE_BUBBLES = 19; + + /** + * Hide soft input when focusing the same window (e.g. screen turned-off and turn-on) which no + * valid focused editor. + * + * Note: From Android R, the window focus change callback is processed by InputDispatcher, + * some focus behavior changes (e.g. There are an activity with a dialog window, after + * screen turned-off and turned-on, before Android R the window focus sequence would be + * the activity first and then the dialog focused, however, in R the focus sequence would be + * only the dialog focused as it's the latest window with input focus) makes we need to hide + * soft-input when the same window focused again to align with the same behavior prior to R. + */ + int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR = 20; } diff --git a/core/java/com/android/internal/inputmethod/StartInputFlags.java b/core/java/com/android/internal/inputmethod/StartInputFlags.java index 5a8d2c227256..ac83987ef12c 100644 --- a/core/java/com/android/internal/inputmethod/StartInputFlags.java +++ b/core/java/com/android/internal/inputmethod/StartInputFlags.java @@ -47,4 +47,10 @@ public @interface StartInputFlags { * documented hence we probably need to revisit this though. */ int INITIAL_CONNECTION = 4; + + /** + * The start input happens when the window gained focus to call + * {@code android.view.inputmethod.InputMethodManager#startInputAsyncOnWindowFocusGain}. + */ + int WINDOW_GAINED_FOCUS = 8; } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 19dc2ed6daea..e60f7fc70757 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -51,6 +51,11 @@ public class InteractionJankMonitor { public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 0; public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 0; public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 0; + public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 0; + public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 0; + public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 0; + public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 0; + public static final int CUJ_LAUNCHER_QUICK_SWITCH = 0; private static final int NO_STATSD_LOGGING = -1; @@ -78,7 +83,12 @@ public class InteractionJankMonitor { CUJ_NOTIFICATION_SHADE_ROW_EXPAND, CUJ_NOTIFICATION_SHADE_ROW_SWIPE, CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, - CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE + CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, + CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS, + CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON, + CUJ_LAUNCHER_APP_CLOSE_TO_HOME, + CUJ_LAUNCHER_APP_CLOSE_TO_PIP, + CUJ_LAUNCHER_QUICK_SWITCH, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType {} diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 4a0e26a5c7cb..308af99d465a 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -145,7 +145,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final boolean DEBUG = false; public static final boolean DEBUG_ENERGY = false; private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY; - private static final boolean DEBUG_BINDER_STATS = true; + private static final boolean DEBUG_BINDER_STATS = false; private static final boolean DEBUG_MEMORY = false; private static final boolean DEBUG_HISTORY = false; private static final boolean USE_OLD_HISTORY = false; // for debugging. @@ -156,7 +156,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - static final int VERSION = 188 + (USE_OLD_HISTORY ? 1000 : 0); + static final int VERSION = 189 + (USE_OLD_HISTORY ? 1000 : 0); // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -221,7 +221,8 @@ public class BatteryStatsImpl extends BatteryStats { @VisibleForTesting protected KernelSingleUidTimeReader mKernelSingleUidTimeReader; @VisibleForTesting - protected SystemServerCpuThreadReader mSystemServerCpuThreadReader; + protected SystemServerCpuThreadReader mSystemServerCpuThreadReader = + SystemServerCpuThreadReader.create(); private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats = new KernelMemoryBandwidthStats(); @@ -1014,14 +1015,21 @@ public class BatteryStatsImpl extends BatteryStats { private long[] mCpuFreqs; /** + * Times spent by the system server process grouped by cluster and CPU speed. + */ + private LongSamplingCounterArray mSystemServerCpuTimesUs; + + private long[] mTmpSystemServerCpuTimesUs; + + /** * Times spent by the system server threads grouped by cluster and CPU speed. */ - private LongSamplingCounter[][] mSystemServerThreadCpuTimesUs; + private LongSamplingCounterArray mSystemServerThreadCpuTimesUs; /** * Times spent by the system server threads handling incoming binder requests. */ - private LongSamplingCounter[][] mBinderThreadCpuTimesUs; + private LongSamplingCounterArray mBinderThreadCpuTimesUs; @VisibleForTesting protected PowerProfile mPowerProfile; @@ -10610,8 +10618,6 @@ public class BatteryStatsImpl extends BatteryStats { firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i); } - mSystemServerCpuThreadReader = SystemServerCpuThreadReader.create(); - if (mEstimatedBatteryCapacity == -1) { // Initialize the estimated battery capacity to a known preset one. mEstimatedBatteryCapacity = (int) mPowerProfile.getBatteryCapacity(); @@ -11291,6 +11297,7 @@ public class BatteryStatsImpl extends BatteryStats { mTmpRailStats.reset(); + resetIfNotNull(mSystemServerCpuTimesUs, false, elapsedRealtimeUs); resetIfNotNull(mSystemServerThreadCpuTimesUs, false, elapsedRealtimeUs); resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs); @@ -12421,38 +12428,58 @@ public class BatteryStatsImpl extends BatteryStats { SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes = mSystemServerCpuThreadReader.readDelta(); + if (systemServiceCpuThreadTimes == null) { + return; + } - int index = 0; int numCpuClusters = mPowerProfile.getNumCpuClusters(); - if (mSystemServerThreadCpuTimesUs == null) { - mSystemServerThreadCpuTimesUs = new LongSamplingCounter[numCpuClusters][]; - mBinderThreadCpuTimesUs = new LongSamplingCounter[numCpuClusters][]; - } - for (int cluster = 0; cluster < numCpuClusters; cluster++) { - int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster); - if (mSystemServerThreadCpuTimesUs[cluster] == null) { - mSystemServerThreadCpuTimesUs[cluster] = new LongSamplingCounter[numSpeeds]; - mBinderThreadCpuTimesUs[cluster] = new LongSamplingCounter[numSpeeds]; - for (int speed = 0; speed < numSpeeds; speed++) { - mSystemServerThreadCpuTimesUs[cluster][speed] = - new LongSamplingCounter(mOnBatteryTimeBase); - mBinderThreadCpuTimesUs[cluster][speed] = - new LongSamplingCounter(mOnBatteryTimeBase); - } - } - for (int speed = 0; speed < numSpeeds; speed++) { - mSystemServerThreadCpuTimesUs[cluster][speed].addCountLocked( - systemServiceCpuThreadTimes.threadCpuTimesUs[index]); - mBinderThreadCpuTimesUs[cluster][speed].addCountLocked( - systemServiceCpuThreadTimes.binderThreadCpuTimesUs[index]); - index++; - } + if (mSystemServerCpuTimesUs == null) { + mSystemServerCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase); + mSystemServerThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase); + mBinderThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase); + } + mSystemServerThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.threadCpuTimesUs); + mBinderThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.binderThreadCpuTimesUs); + + long totalCpuTimeAllThreads = 0; + for (int index = systemServiceCpuThreadTimes.threadCpuTimesUs.length - 1; index >= 0; + index--) { + totalCpuTimeAllThreads += systemServiceCpuThreadTimes.threadCpuTimesUs[index]; + } + + // Estimate per cluster per frequency CPU time for the entire process + // by distributing the total process CPU time proportionately to how much + // CPU time its threads took on those clusters/frequencies. This algorithm + // works more accurately when when we have equally distributed concurrency. + // TODO(b/169279846): obtain actual process CPU times from the kernel + long processCpuTime = systemServiceCpuThreadTimes.processCpuTimeUs; + if (mTmpSystemServerCpuTimesUs == null) { + mTmpSystemServerCpuTimesUs = + new long[systemServiceCpuThreadTimes.threadCpuTimesUs.length]; } + for (int index = systemServiceCpuThreadTimes.threadCpuTimesUs.length - 1; index >= 0; + index--) { + mTmpSystemServerCpuTimesUs[index] = + processCpuTime * systemServiceCpuThreadTimes.threadCpuTimesUs[index] + / totalCpuTimeAllThreads; + + } + + mSystemServerCpuTimesUs.addCountLocked(mTmpSystemServerCpuTimesUs); + if (DEBUG_BINDER_STATS) { Slog.d(TAG, "System server threads per CPU cluster (binder threads/total threads/%)"); - long binderThreadTimeMs = 0; + long totalCpuTimeMs = 0; long totalThreadTimeMs = 0; + long binderThreadTimeMs = 0; int cpuIndex = 0; + final long[] systemServerCpuTimesUs = + mSystemServerCpuTimesUs.getCountsLocked(0); + final long[] systemServerThreadCpuTimesUs = + mSystemServerThreadCpuTimesUs.getCountsLocked(0); + final long[] binderThreadCpuTimesUs = + mBinderThreadCpuTimesUs.getCountsLocked(0); + int index = 0; for (int cluster = 0; cluster < numCpuClusters; cluster++) { StringBuilder sb = new StringBuilder(); sb.append("cpu").append(cpuIndex).append(": ["); @@ -12461,15 +12488,14 @@ public class BatteryStatsImpl extends BatteryStats { if (speed != 0) { sb.append(", "); } - long totalCountMs = - mSystemServerThreadCpuTimesUs[cluster][speed].getCountLocked(0) / 1000; - long binderCountMs = mBinderThreadCpuTimesUs[cluster][speed].getCountLocked(0) - / 1000; + long totalCountMs = systemServerThreadCpuTimesUs[index] / 1000; + long binderCountMs = binderThreadCpuTimesUs[index] / 1000; sb.append(String.format("%d/%d(%.1f%%)", binderCountMs, totalCountMs, totalCountMs != 0 ? (double) binderCountMs * 100 / totalCountMs : 0)); + totalCpuTimeMs += systemServerCpuTimesUs[index] / 1000; totalThreadTimeMs += totalCountMs; binderThreadTimeMs += binderCountMs; index++; @@ -12477,6 +12503,8 @@ public class BatteryStatsImpl extends BatteryStats { cpuIndex += mPowerProfile.getNumCoresInCpuCluster(cluster); Slog.d(TAG, sb.toString()); } + + Slog.d(TAG, "Total system server CPU time (ms): " + totalCpuTimeMs); Slog.d(TAG, "Total system server thread time (ms): " + totalThreadTimeMs); Slog.d(TAG, String.format("Total Binder thread time (ms): %d (%.1f%%)", binderThreadTimeMs, @@ -13715,7 +13743,7 @@ public class BatteryStatsImpl extends BatteryStats { @Override - public long getSystemServiceTimeAtCpuSpeed(int cluster, int step) { + public long[] getSystemServiceTimeAtCpuSpeeds() { // Estimates the time spent by the system server handling incoming binder requests. // // The data that we can get from the kernel is this: @@ -13731,7 +13759,7 @@ public class BatteryStatsImpl extends BatteryStats { // - These 10 threads spent 1000 ms of CPU time in aggregate // - Of the 10 threads 4 were execute exclusively incoming binder calls. // - These 4 "binder" threads consumed 600 ms of CPU time in aggregate - // - The real time spent by the system server UID doing all of this is, say, 200 ms. + // - The real time spent by the system server process doing all of this is, say, 200 ms. // // We will assume that power consumption is proportional to the time spent by the CPU // across all threads. This is a crude assumption, but we don't have more detailed data. @@ -13745,41 +13773,29 @@ public class BatteryStatsImpl extends BatteryStats { // of the total power consumed by incoming binder calls for the given cluster/speed // combination. - if (mSystemServerThreadCpuTimesUs == null) { - return 0; - } - - if (cluster < 0 || cluster >= mSystemServerThreadCpuTimesUs.length) { - return 0; - } - - final LongSamplingCounter[] threadTimesForCluster = mSystemServerThreadCpuTimesUs[cluster]; - - if (step < 0 || step >= threadTimesForCluster.length) { - return 0; - } - - Uid systemUid = mUidStats.get(Process.SYSTEM_UID); - if (systemUid == null) { - return 0; + if (mSystemServerCpuTimesUs == null) { + return null; } - final long uidTimeAtCpuSpeedUs = systemUid.getTimeAtCpuSpeed(cluster, step, + final long[] systemServerCpuTimesUs = mSystemServerCpuTimesUs.getCountsLocked( + BatteryStats.STATS_SINCE_CHARGED); + final long [] systemServerThreadCpuTimesUs = mSystemServerThreadCpuTimesUs.getCountsLocked( + BatteryStats.STATS_SINCE_CHARGED); + final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked( BatteryStats.STATS_SINCE_CHARGED); - if (uidTimeAtCpuSpeedUs == 0) { - return 0; - } - final long uidThreadTimeUs = - threadTimesForCluster[step].getCountLocked(BatteryStats.STATS_SINCE_CHARGED); + final int size = systemServerCpuTimesUs.length; + final long[] results = new long[size]; - if (uidThreadTimeUs == 0) { - return 0; - } + for (int i = 0; i < size; i++) { + if (systemServerThreadCpuTimesUs[i] == 0) { + continue; + } - final long binderThreadTimeUs = mBinderThreadCpuTimesUs[cluster][step].getCountLocked( - BatteryStats.STATS_SINCE_CHARGED); - return uidTimeAtCpuSpeedUs * binderThreadTimeUs / uidThreadTimeUs; + results[i] = systemServerCpuTimesUs[i] * binderThreadCpuTimesUs[i] + / systemServerThreadCpuTimesUs[i]; + } + return results; } /** @@ -14181,18 +14197,14 @@ public class BatteryStatsImpl extends BatteryStats { updateSystemServiceCallStats(); if (mSystemServerThreadCpuTimesUs != null) { pw.println("Per UID System server binder time in ms:"); + long[] systemServiceTimeAtCpuSpeeds = getSystemServiceTimeAtCpuSpeeds(); for (int i = 0; i < size; i++) { int u = mUidStats.keyAt(i); Uid uid = mUidStats.get(u); double proportionalSystemServiceUsage = uid.getProportionalSystemServiceUsage(); - long timeUs = 0; - for (int cluster = 0; cluster < mSystemServerThreadCpuTimesUs.length; cluster++) { - int numSpeeds = mSystemServerThreadCpuTimesUs[cluster].length; - for (int speed = 0; speed < numSpeeds; speed++) { - timeUs += getSystemServiceTimeAtCpuSpeed(cluster, speed) - * proportionalSystemServiceUsage; - } + for (int j = systemServiceTimeAtCpuSpeeds.length - 1; j >= 0; j--) { + timeUs += systemServiceTimeAtCpuSpeeds[j] * proportionalSystemServiceUsage; } pw.print(" "); @@ -15728,8 +15740,10 @@ public class BatteryStatsImpl extends BatteryStats { mUidStats.append(uid, u); } - mSystemServerThreadCpuTimesUs = readCpuSpeedCountersFromParcel(in); - mBinderThreadCpuTimesUs = readCpuSpeedCountersFromParcel(in); + mSystemServerCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase); + mSystemServerThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, + mOnBatteryTimeBase); + mBinderThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase); } public void writeToParcel(Parcel out, int flags) { @@ -15778,7 +15792,7 @@ public class BatteryStatsImpl extends BatteryStats { mScreenOnTimer.writeToParcel(out, uSecRealtime); mScreenDozeTimer.writeToParcel(out, uSecRealtime); - for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { + for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; i++) { mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime); } mInteractiveTimer.writeToParcel(out, uSecRealtime); @@ -15794,7 +15808,7 @@ public class BatteryStatsImpl extends BatteryStats { mPhoneSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime); } mPhoneSignalScanningTimer.writeToParcel(out, uSecRealtime); - for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { + for (int i = 0; i < NUM_DATA_CONNECTION_TYPES; i++) { mPhoneDataConnectionsTimer[i].writeToParcel(out, uSecRealtime); } for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { @@ -15809,18 +15823,18 @@ public class BatteryStatsImpl extends BatteryStats { mWifiMulticastWakelockTimer.writeToParcel(out, uSecRealtime); mWifiOnTimer.writeToParcel(out, uSecRealtime); mGlobalWifiRunningTimer.writeToParcel(out, uSecRealtime); - for (int i=0; i<NUM_WIFI_STATES; i++) { + for (int i = 0; i < NUM_WIFI_STATES; i++) { mWifiStateTimer[i].writeToParcel(out, uSecRealtime); } - for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { + for (int i = 0; i < NUM_WIFI_SUPPL_STATES; i++) { mWifiSupplStateTimer[i].writeToParcel(out, uSecRealtime); } - for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { mWifiSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime); } mWifiActiveTimer.writeToParcel(out, uSecRealtime); mWifiActivity.writeToParcel(out, 0); - for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { + for (int i = 0; i < GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { mGpsSignalQualityTimer[i].writeToParcel(out, uSecRealtime); } mBluetoothActivity.writeToParcel(out, 0); @@ -15930,8 +15944,9 @@ public class BatteryStatsImpl extends BatteryStats { } else { out.writeInt(0); } - writeCpuSpeedCountersToParcel(out, mSystemServerThreadCpuTimesUs); - writeCpuSpeedCountersToParcel(out, mBinderThreadCpuTimesUs); + LongSamplingCounterArray.writeToParcel(out, mSystemServerCpuTimesUs); + LongSamplingCounterArray.writeToParcel(out, mSystemServerThreadCpuTimesUs); + LongSamplingCounterArray.writeToParcel(out, mBinderThreadCpuTimesUs); } private void writeCpuSpeedCountersToParcel(Parcel out, LongSamplingCounter[][] counters) { diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java new file mode 100644 index 000000000000..0578b8976037 --- /dev/null +++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java @@ -0,0 +1,259 @@ +/* + * 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 android.os.Process.PROC_OUT_LONG; +import static android.os.Process.PROC_SPACE_TERM; + +import android.annotation.Nullable; +import android.os.Process; +import android.system.Os; +import android.system.OsConstants; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.IOException; +import java.nio.file.DirectoryIteratorException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +/** + * Iterates over all threads owned by a given process, and return the CPU usage for + * each thread. The CPU usage statistics contain the amount of time spent in a frequency band. CPU + * usage is collected using {@link ProcTimeInStateReader}. + */ +public class KernelSingleProcessCpuThreadReader { + + private static final String TAG = "KernelSingleProcCpuThreadRdr"; + + private static final boolean DEBUG = false; + + /** + * The name of the file to read CPU statistics from, must be found in {@code + * /proc/$PID/task/$TID} + */ + private static final String CPU_STATISTICS_FILENAME = "time_in_state"; + + private static final String PROC_STAT_FILENAME = "stat"; + + /** Directory under /proc/$PID containing CPU stats files for threads */ + public static final String THREAD_CPU_STATS_DIRECTORY = "task"; + + /** Default mount location of the {@code proc} filesystem */ + private static final Path DEFAULT_PROC_PATH = Paths.get("/proc"); + + /** The initial {@code time_in_state} file for {@link ProcTimeInStateReader} */ + private static final Path INITIAL_TIME_IN_STATE_PATH = Paths.get("self/time_in_state"); + + /** See https://man7.org/linux/man-pages/man5/proc.5.html */ + private static final int[] PROCESS_FULL_STATS_FORMAT = new int[]{ + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM, + PROC_SPACE_TERM | PROC_OUT_LONG, // 14: utime + PROC_SPACE_TERM | PROC_OUT_LONG, // 15: stime + // Ignore remaining fields + }; + + private final long[] mProcessFullStatsData = new long[2]; + + private static final int PROCESS_FULL_STAT_UTIME = 0; + private static final int PROCESS_FULL_STAT_STIME = 1; + + /** Used to read and parse {@code time_in_state} files */ + private final ProcTimeInStateReader mProcTimeInStateReader; + + private final int mPid; + + /** Where the proc filesystem is mounted */ + private final Path mProcPath; + + // How long a CPU jiffy is in milliseconds. + private final long mJiffyMillis; + + // Path: /proc/<pid>/stat + private final String mProcessStatFilePath; + + // Path: /proc/<pid>/task + private final Path mThreadsDirectoryPath; + + /** + * Count of frequencies read from the {@code time_in_state} file. Read from {@link + * #mProcTimeInStateReader#getCpuFrequenciesKhz()}. + */ + private int mFrequencyCount; + + /** + * Create with a path where `proc` is mounted. Used primarily for testing + * + * @param pid PID of the process whose threads are to be read. + * @param procPath where `proc` is mounted (to find, see {@code mount | grep ^proc}) + */ + @VisibleForTesting + public KernelSingleProcessCpuThreadReader( + int pid, + Path procPath) throws IOException { + mPid = pid; + mProcPath = procPath; + mProcTimeInStateReader = new ProcTimeInStateReader( + mProcPath.resolve(INITIAL_TIME_IN_STATE_PATH)); + long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK); + mJiffyMillis = 1000 / jiffyHz; + mProcessStatFilePath = + mProcPath.resolve(String.valueOf(mPid)).resolve(PROC_STAT_FILENAME).toString(); + mThreadsDirectoryPath = + mProcPath.resolve(String.valueOf(mPid)).resolve(THREAD_CPU_STATS_DIRECTORY); + } + + /** + * Create the reader and handle exceptions during creation + * + * @return the reader, null if an exception was thrown during creation + */ + @Nullable + public static KernelSingleProcessCpuThreadReader create(int pid) { + try { + return new KernelSingleProcessCpuThreadReader(pid, DEFAULT_PROC_PATH); + } catch (IOException e) { + Slog.e(TAG, "Failed to initialize KernelSingleProcessCpuThreadReader", e); + return null; + } + } + + /** + * Get the CPU frequencies that correspond to the times reported in {@link + * ThreadCpuUsage#usageTimesMillis} + */ + public int getCpuFrequencyCount() { + if (mFrequencyCount == 0) { + mFrequencyCount = mProcTimeInStateReader.getFrequenciesKhz().length; + } + return mFrequencyCount; + } + + /** + * Get the total and per-thread CPU usage of the process with the PID specified in the + * constructor. + */ + @Nullable + public ProcessCpuUsage getProcessCpuUsage() { + if (DEBUG) { + Slog.d(TAG, "Reading CPU thread usages with directory " + mProcPath + " process ID " + + mPid); + } + + if (!Process.readProcFile(mProcessStatFilePath, PROCESS_FULL_STATS_FORMAT, null, + mProcessFullStatsData, null)) { + Slog.e(TAG, "Failed to read process stat file " + mProcessStatFilePath); + return null; + } + + long utime = mProcessFullStatsData[PROCESS_FULL_STAT_UTIME]; + long stime = mProcessFullStatsData[PROCESS_FULL_STAT_STIME]; + + long processCpuTimeMillis = (utime + stime) * mJiffyMillis; + + final ArrayList<ThreadCpuUsage> threadCpuUsages = new ArrayList<>(); + try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(mThreadsDirectoryPath)) { + for (Path threadDirectory : threadPaths) { + ThreadCpuUsage threadCpuUsage = getThreadCpuUsage(threadDirectory); + if (threadCpuUsage == null) { + continue; + } + threadCpuUsages.add(threadCpuUsage); + } + } catch (IOException | DirectoryIteratorException e) { + // Expected when a process finishes + return null; + } + + // If we found no threads, then the process has exited while we were reading from it + if (threadCpuUsages.isEmpty()) { + return null; + } + if (DEBUG) { + Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads"); + } + return new ProcessCpuUsage(processCpuTimeMillis, threadCpuUsages); + } + + /** + * Get a thread's CPU usage + * + * @param threadDirectory the {@code /proc} directory of the thread + * @return thread CPU usage. Null if the thread exited and its {@code proc} directory was + * removed while collecting information + */ + @Nullable + private ThreadCpuUsage getThreadCpuUsage(Path threadDirectory) { + // Get the thread ID from the directory name + final int threadId; + try { + final String directoryName = threadDirectory.getFileName().toString(); + threadId = Integer.parseInt(directoryName); + } catch (NumberFormatException e) { + Slog.w(TAG, "Failed to parse thread ID when iterating over /proc/*/task", e); + return null; + } + + // Get the CPU statistics from the directory + final Path threadCpuStatPath = threadDirectory.resolve(CPU_STATISTICS_FILENAME); + final long[] cpuUsages = mProcTimeInStateReader.getUsageTimesMillis(threadCpuStatPath); + if (cpuUsages == null) { + return null; + } + + return new ThreadCpuUsage(threadId, cpuUsages); + } + + /** CPU usage of a process and all of its threads */ + public static class ProcessCpuUsage { + public final long cpuTimeMillis; + public final List<ThreadCpuUsage> threadCpuUsages; + + ProcessCpuUsage(long cpuTimeMillis, List<ThreadCpuUsage> threadCpuUsages) { + this.cpuTimeMillis = cpuTimeMillis; + this.threadCpuUsages = threadCpuUsages; + } + } + + /** CPU usage of a thread */ + public static class ThreadCpuUsage { + public final int threadId; + public final long[] usageTimesMillis; + + ThreadCpuUsage(int threadId, long[] usageTimesMillis) { + this.threadId = threadId; + this.usageTimesMillis = usageTimesMillis; + } + } +} diff --git a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java index 3aa2390375ec..d9f0dc0ae795 100644 --- a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java +++ b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java @@ -16,25 +16,28 @@ package com.android.internal.os; +import android.annotation.Nullable; import android.os.Process; import com.android.internal.annotations.VisibleForTesting; import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * Reads /proc/UID/task/TID/time_in_state files to obtain statistics on CPU usage * by various threads of the System Server. */ public class SystemServerCpuThreadReader { - private KernelCpuThreadReader mKernelCpuThreadReader; + private final KernelSingleProcessCpuThreadReader mKernelCpuThreadReader; private int[] mBinderThreadNativeTids = new int[0]; // Sorted - private int[] mThreadCpuTimesUs; - private int[] mBinderThreadCpuTimesUs; + private long mProcessCpuTimeUs; + private long[] mThreadCpuTimesUs; + private long[] mBinderThreadCpuTimesUs; + private long mLastProcessCpuTimeUs; private long[] mLastThreadCpuTimesUs; private long[] mLastBinderThreadCpuTimesUs; @@ -42,6 +45,8 @@ public class SystemServerCpuThreadReader { * Times (in microseconds) spent by the system server UID. */ public static class SystemServiceCpuThreadTimes { + // The entire process + public long processCpuTimeUs; // All threads public long[] threadCpuTimesUs; // Just the threads handling incoming binder calls @@ -55,22 +60,16 @@ public class SystemServerCpuThreadReader { */ public static SystemServerCpuThreadReader create() { return new SystemServerCpuThreadReader( - KernelCpuThreadReader.create(0, uid -> uid == Process.myUid())); + KernelSingleProcessCpuThreadReader.create(Process.myPid())); } @VisibleForTesting - public SystemServerCpuThreadReader(Path procPath, int systemServerUid) throws IOException { - this(new KernelCpuThreadReader(0, uid -> uid == systemServerUid, null, null, - new KernelCpuThreadReader.Injector() { - @Override - public int getUidForPid(int pid) { - return systemServerUid; - } - })); + public SystemServerCpuThreadReader(Path procPath, int pid) throws IOException { + this(new KernelSingleProcessCpuThreadReader(pid, procPath)); } @VisibleForTesting - public SystemServerCpuThreadReader(KernelCpuThreadReader kernelCpuThreadReader) { + public SystemServerCpuThreadReader(KernelSingleProcessCpuThreadReader kernelCpuThreadReader) { mKernelCpuThreadReader = kernelCpuThreadReader; } @@ -82,11 +81,12 @@ public class SystemServerCpuThreadReader { /** * Returns delta of CPU times, per thread, since the previous call to this method. */ + @Nullable public SystemServiceCpuThreadTimes readDelta() { + int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequencyCount(); if (mBinderThreadCpuTimesUs == null) { - int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequenciesKhz().length; - mThreadCpuTimesUs = new int[numCpuFrequencies]; - mBinderThreadCpuTimesUs = new int[numCpuFrequencies]; + mThreadCpuTimesUs = new long[numCpuFrequencies]; + mBinderThreadCpuTimesUs = new long[numCpuFrequencies]; mLastThreadCpuTimesUs = new long[numCpuFrequencies]; mLastBinderThreadCpuTimesUs = new long[numCpuFrequencies]; @@ -95,49 +95,47 @@ public class SystemServerCpuThreadReader { mDeltaCpuThreadTimes.binderThreadCpuTimesUs = new long[numCpuFrequencies]; } + mProcessCpuTimeUs = 0; Arrays.fill(mThreadCpuTimesUs, 0); Arrays.fill(mBinderThreadCpuTimesUs, 0); - ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsage = + KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage = mKernelCpuThreadReader.getProcessCpuUsage(); - int processCpuUsageSize = processCpuUsage.size(); - for (int i = 0; i < processCpuUsageSize; i++) { - KernelCpuThreadReader.ProcessCpuUsage pcu = processCpuUsage.get(i); - ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = pcu.threadCpuUsages; - if (threadCpuUsages != null) { - int threadCpuUsagesSize = threadCpuUsages.size(); - for (int j = 0; j < threadCpuUsagesSize; j++) { - KernelCpuThreadReader.ThreadCpuUsage tcu = threadCpuUsages.get(j); - boolean isBinderThread = - Arrays.binarySearch(mBinderThreadNativeTids, tcu.threadId) >= 0; - - final int len = Math.min(tcu.usageTimesMillis.length, mThreadCpuTimesUs.length); - for (int k = 0; k < len; k++) { - int usageTimeUs = tcu.usageTimesMillis[k] * 1000; - mThreadCpuTimesUs[k] += usageTimeUs; - if (isBinderThread) { - mBinderThreadCpuTimesUs[k] += usageTimeUs; - } - } + if (processCpuUsage == null) { + return null; + } + + mProcessCpuTimeUs = processCpuUsage.cpuTimeMillis * 1000; + + List<KernelSingleProcessCpuThreadReader.ThreadCpuUsage> threadCpuUsages = + processCpuUsage.threadCpuUsages; + int threadCpuUsagesSize = threadCpuUsages.size(); + for (int i = 0; i < threadCpuUsagesSize; i++) { + KernelSingleProcessCpuThreadReader.ThreadCpuUsage tcu = threadCpuUsages.get(i); + boolean isBinderThread = + Arrays.binarySearch(mBinderThreadNativeTids, tcu.threadId) >= 0; + for (int k = 0; k < numCpuFrequencies; k++) { + long usageTimeUs = tcu.usageTimesMillis[k] * 1000; + mThreadCpuTimesUs[k] += usageTimeUs; + if (isBinderThread) { + mBinderThreadCpuTimesUs[k] += usageTimeUs; } } } for (int i = 0; i < mThreadCpuTimesUs.length; i++) { - if (mThreadCpuTimesUs[i] < mLastThreadCpuTimesUs[i]) { - mDeltaCpuThreadTimes.threadCpuTimesUs[i] = mThreadCpuTimesUs[i]; - mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] = mBinderThreadCpuTimesUs[i]; - } else { - mDeltaCpuThreadTimes.threadCpuTimesUs[i] = - mThreadCpuTimesUs[i] - mLastThreadCpuTimesUs[i]; - mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] = - mBinderThreadCpuTimesUs[i] - mLastBinderThreadCpuTimesUs[i]; - } + mDeltaCpuThreadTimes.processCpuTimeUs = + Math.max(0, mProcessCpuTimeUs - mLastProcessCpuTimeUs); + mDeltaCpuThreadTimes.threadCpuTimesUs[i] = + Math.max(0, mThreadCpuTimesUs[i] - mLastThreadCpuTimesUs[i]); + mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] = + Math.max(0, mBinderThreadCpuTimesUs[i] - mLastBinderThreadCpuTimesUs[i]); mLastThreadCpuTimesUs[i] = mThreadCpuTimesUs[i]; mLastBinderThreadCpuTimesUs[i] = mBinderThreadCpuTimesUs[i]; } + mLastProcessCpuTimeUs = mProcessCpuTimeUs; + return mDeltaCpuThreadTimes; } - } diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java index 481b901b3c69..fc36e50950e9 100644 --- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java +++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java @@ -34,7 +34,9 @@ public class SystemServicePowerCalculator extends PowerCalculator { private final PowerProfile mPowerProfile; private final BatteryStats mBatteryStats; // Tracks system server CPU [cluster][speed] power in milliAmp-microseconds - private double[][] mSystemServicePowerMaUs; + // Data organized like this: + // {cluster1-speed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...} + private double[] mSystemServicePowerMaUs; public SystemServicePowerCalculator(PowerProfile powerProfile, BatteryStats batteryStats) { mPowerProfile = powerProfile; @@ -50,37 +52,41 @@ public class SystemServicePowerCalculator extends PowerCalculator { updateSystemServicePower(); } - double cpuPowerMaUs = 0; - int numCpuClusters = mPowerProfile.getNumCpuClusters(); - for (int cluster = 0; cluster < numCpuClusters; cluster++) { - final int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster); - for (int speed = 0; speed < numSpeeds; speed++) { - cpuPowerMaUs += mSystemServicePowerMaUs[cluster][speed] * proportionalUsage; + if (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 = cpuPowerMaUs / MICROSEC_IN_HR; + } } } private void updateSystemServicePower() { + final long[] systemServiceTimeAtCpuSpeeds = mBatteryStats.getSystemServiceTimeAtCpuSpeeds(); + if (systemServiceTimeAtCpuSpeeds == null) { + return; + } + + if (mSystemServicePowerMaUs == null) { + mSystemServicePowerMaUs = new double[systemServiceTimeAtCpuSpeeds.length]; + } + int index = 0; final int numCpuClusters = mPowerProfile.getNumCpuClusters(); - mSystemServicePowerMaUs = new double[numCpuClusters][]; for (int cluster = 0; cluster < numCpuClusters; cluster++) { final int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster); - mSystemServicePowerMaUs[cluster] = new double[numSpeeds]; for (int speed = 0; speed < numSpeeds; speed++) { - mSystemServicePowerMaUs[cluster][speed] = - mBatteryStats.getSystemServiceTimeAtCpuSpeed(cluster, speed) + mSystemServicePowerMaUs[index] = + systemServiceTimeAtCpuSpeeds[index] * mPowerProfile.getAveragePowerForCpuCore(cluster, speed); + index++; } } + if (DEBUG) { - Log.d(TAG, "System service power per CPU cluster and frequency"); - for (int cluster = 0; cluster < numCpuClusters; cluster++) { - Log.d(TAG, "Cluster[" + cluster + "]: " - + Arrays.toString(mSystemServicePowerMaUs[cluster])); - } + Log.d(TAG, "System service power per CPU cluster and frequency:" + + Arrays.toString(mSystemServicePowerMaUs)); } } diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index 9874c6aabf04..50ba42fdae2f 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -74,6 +74,8 @@ public enum ProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM), WM_DEBUG_SYNC_ENGINE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), + WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, + Consts.TAG_WM), TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index cc2934fd8dc5..caae5188b6a0 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -242,4 +242,10 @@ oneway interface IStatusBar * @param connect {@code true} if needs connection, otherwise set the connection to null. */ void requestWindowMagnificationConnection(boolean connect); + + /** + * Allow for pass-through arguments from `adb shell cmd statusbar <args>`, and write to the + * file descriptor passed in. + */ + void passThroughShellCommand(in String[] args, in ParcelFileDescriptor pfd); } diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index d5f54a199828..fff9ac9e49b7 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -43,7 +43,6 @@ public class BaseIWindow extends IWindow.Stub { public BaseIWindow() {} private IWindowSession mSession; - public int mSeq; public void setSession(IWindowSession session) { mSession = session; @@ -140,12 +139,6 @@ public class BaseIWindow extends IWindow.Stub { } @Override - public void dispatchSystemUiVisibilityChanged(int seq, int globalUi, - int localValue, int localChanges) { - mSeq = seq; - } - - @Override public void dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras, boolean sync) { if (sync) { diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl index 45090320c192..c9443b002133 100644 --- a/core/java/com/android/internal/view/IInputMethodClient.aidl +++ b/core/java/com/android/internal/view/IInputMethodClient.aidl @@ -33,4 +33,5 @@ oneway interface IInputMethodClient { void reportPreRendered(in EditorInfo info); void applyImeVisibility(boolean setVisible); void updateActivityViewToScreenMatrix(int bindSequence, in float[] matrixValues); + void setImeTraceEnabled(boolean enabled); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index a1cbd3fcae79..5a06273bb173 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -77,4 +77,6 @@ interface IInputMethodManager { void removeImeSurface(); /** Remove the IME surface. Requires passing the currently focused window. */ void removeImeSurfaceFromWindow(in IBinder windowToken); + void startProtoDump(in byte[] clientProtoDump); + boolean isImeTraceEnabled(); } diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java index ddee81a649af..ff3543c837eb 100644 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ b/core/java/com/android/internal/widget/EditableInputConnection.java @@ -17,9 +17,6 @@ package com.android.internal.widget; import android.compat.annotation.UnsupportedAppUsage; -import android.content.ClipData; -import android.content.ClipDescription; -import android.os.Build; import android.os.Bundle; import android.text.Editable; import android.text.method.KeyListener; @@ -30,8 +27,6 @@ import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputContentInfo; -import android.widget.RichContentReceiver; import android.widget.TextView; public class EditableInputConnection extends BaseInputConnection { @@ -186,28 +181,6 @@ public class EditableInputConnection extends BaseInputConnection { } @Override - public boolean commitContent(InputContentInfo content, int flags, Bundle opts) { - int targetSdkVersion = mTextView.getContext().getApplicationInfo().targetSdkVersion; - if (targetSdkVersion <= Build.VERSION_CODES.R) { - return false; - } - - final ClipDescription description = content.getDescription(); - final RichContentReceiver<TextView> receiver = mTextView.getRichContentReceiver(); - if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { - try { - content.requestPermission(); - } catch (Exception e) { - // TODO(b/147299828): Can we catch SecurityException instead? - Log.w(TAG, "Can't insert content from IME; requestPermission() failed: " + e); - return false; // Can't insert the content if we don't have permission to read it - } - } - ClipData clip = new ClipData(description, new ClipData.Item(content.getContentUri())); - return receiver.onReceive(mTextView, clip, RichContentReceiver.SOURCE_INPUT_METHOD, 0); - } - - @Override public boolean requestCursorUpdates(int cursorUpdateMode) { if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode); diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 396a84ffcfb8..ed663cfeb613 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -46,6 +46,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; +import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -55,7 +56,6 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; -import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -95,7 +95,7 @@ public class SystemConfig { private static final String VENDOR_SKU_PROPERTY = "ro.boot.product.vendor.sku"; // Group-ids that are given to all packages as read from etc/permissions/*.xml. - int[] mGlobalGids; + int[] mGlobalGids = EmptyArray.INT; // These are the built-in uid -> permission mappings that were read from the // system configuration files. diff --git a/core/jni/Android.bp b/core/jni/Android.bp index b4b58ff903db..adb0fadbc49a 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -317,4 +317,9 @@ cc_library_shared { cflags: ["-DANDROID_EXPERIMENTAL_MTE"], }, }, + + // Workaround Clang LTO crash. + lto: { + never: true, + }, } diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp index be68c4aee278..243540693785 100644 --- a/core/jni/android_database_CursorWindow.cpp +++ b/core/jni/android_database_CursorWindow.cpp @@ -84,23 +84,31 @@ static int getFdCount() { } static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) { + status_t status; String8 name; + CursorWindow* window; + const char* nameStr = env->GetStringUTFChars(nameObj, NULL); name.setTo(nameStr); env->ReleaseStringUTFChars(nameObj, nameStr); - CursorWindow* window; - status_t status = CursorWindow::create(name, cursorWindowSize, &window); + if (cursorWindowSize < 0) { + status = INVALID_OPERATION; + goto fail; + } + status = CursorWindow::create(name, cursorWindowSize, &window); if (status || !window) { - jniThrowExceptionFmt(env, - "android/database/CursorWindowAllocationException", - "Could not allocate CursorWindow '%s' of size %d due to error %d.", - name.string(), cursorWindowSize, status); - return 0; + goto fail; } LOG_WINDOW("nativeInitializeEmpty: window = %p", window); return reinterpret_cast<jlong>(window); + +fail: + jniThrowExceptionFmt(env, "android/database/CursorWindowAllocationException", + "Could not allocate CursorWindow '%s' of size %d due to error %d.", + name.string(), cursorWindowSize, status); + return 0; } static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) { diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index 23f4325c0ff1..a30c37bbd11c 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -29,10 +29,21 @@ namespace android { -static jlong nativeCreate(JNIEnv* env, jclass clazz, jlong surfaceControl, jlong width, jlong height, - jboolean enableTripleBuffering) { - sp<BLASTBufferQueue> queue = new BLASTBufferQueue( - reinterpret_cast<SurfaceControl*>(surfaceControl), width, height, enableTripleBuffering); +static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl, + jlong width, jlong height, jboolean enableTripleBuffering) { + String8 str8; + if (jName) { + const jchar* str16 = env->GetStringCritical(jName, nullptr); + if (str16) { + str8 = String8(reinterpret_cast<const char16_t*>(str16), env->GetStringLength(jName)); + env->ReleaseStringCritical(jName, str16); + str16 = nullptr; + } + } + std::string name = str8.string(); + sp<BLASTBufferQueue> queue = + new BLASTBufferQueue(name, reinterpret_cast<SurfaceControl*>(surfaceControl), width, + height, enableTripleBuffering); queue->incStrong((void*)nativeCreate); return reinterpret_cast<jlong>(queue.get()); } @@ -59,18 +70,12 @@ static void nativeUpdate(JNIEnv*env, jclass clazz, jlong ptr, jlong surfaceContr } static const JNINativeMethod gMethods[] = { - /* name, signature, funcPtr */ - { "nativeCreate", "(JJJZ)J", - (void*)nativeCreate }, - { "nativeGetSurface", "(J)Landroid/view/Surface;", - (void*)nativeGetSurface }, - { "nativeDestroy", "(J)V", - (void*)nativeDestroy }, - { "nativeSetNextTransaction", "(JJ)V", - (void*)nativeSetNextTransaction }, - { "nativeUpdate", "(JJJJ)V", - (void*)nativeUpdate } -}; + /* name, signature, funcPtr */ + {"nativeCreate", "(Ljava/lang/String;JJJZ)J", (void*)nativeCreate}, + {"nativeGetSurface", "(J)Landroid/view/Surface;", (void*)nativeGetSurface}, + {"nativeDestroy", "(J)V", (void*)nativeDestroy}, + {"nativeSetNextTransaction", "(JJ)V", (void*)nativeSetNextTransaction}, + {"nativeUpdate", "(JJJJ)V", (void*)nativeUpdate}}; int register_android_graphics_BLASTBufferQueue(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "android/graphics/BLASTBufferQueue", diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp index 859b40afb7c4..919e3513e061 100644 --- a/core/jni/android_hardware_camera2_CameraMetadata.cpp +++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp @@ -249,6 +249,16 @@ static jint CameraMetadata_getEntryCount(JNIEnv *env, jclass thiz, jlong ptr) { return metadata->entryCount(); } +static jlong CameraMetadata_getBufferSize(JNIEnv *env, jclass thiz, jlong ptr) { + ALOGV("%s", __FUNCTION__); + + CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, ptr); + + if (metadata == NULL) return 0; + + return metadata->bufferSize(); +} + // idempotent. calling more than once has no effect. static void CameraMetadata_close(JNIEnv *env, jclass thiz, jlong ptr) { ALOGV("%s", __FUNCTION__); @@ -561,6 +571,9 @@ static const JNINativeMethod gCameraMetadataMethods[] = { { "nativeGetEntryCount", "(J)I", (void*)CameraMetadata_getEntryCount }, + { "nativeGetBufferSize", + "(J)J", + (void*)CameraMetadata_getBufferSize }, { "nativeClose", "(J)V", (void*)CameraMetadata_close }, diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index 4f3f283859b4..b40491a49b14 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -16,6 +16,8 @@ #define LOG_TAG "GraphicsEnvironment" +#include <vector> + #include <graphicsenv/GraphicsEnv.h> #include <nativehelper/ScopedUtfChars.h> #include <nativeloader/native_loader.h> @@ -47,16 +49,36 @@ void setGpuStats_native(JNIEnv* env, jobject clazz, jstring driverPackageName, appPackageNameChars.c_str(), vulkanVersion); } -void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName, jstring devOptIn, - jobject rulesFd, jlong rulesOffset, jlong rulesLength) { +void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName, + jstring devOptIn, jobjectArray featuresObj, jobject rulesFd, + jlong rulesOffset, jlong rulesLength) { ScopedUtfChars pathChars(env, path); ScopedUtfChars appNameChars(env, appName); ScopedUtfChars devOptInChars(env, devOptIn); + std::vector<std::string> features; + if (featuresObj != nullptr) { + jsize length = env->GetArrayLength(featuresObj); + for (jsize i = 0; i < length; ++i) { + jstring jstr = static_cast<jstring>(env->GetObjectArrayElement(featuresObj, i)); + // null entries are ignored + if (jstr == nullptr) { + continue; + } + const char* cstr = env->GetStringUTFChars(jstr, nullptr); + if (cstr == nullptr) { + continue; + } + features.emplace_back(cstr); + env->ReleaseStringUTFChars(jstr, cstr); + } + } + int rulesFd_native = jniGetFDFromFileDescriptor(env, rulesFd); android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(), - devOptInChars.c_str(), rulesFd_native, rulesOffset, rulesLength); + devOptInChars.c_str(), features, + rulesFd_native, rulesOffset, rulesLength); } bool shouldUseAngle_native(JNIEnv* env, jobject clazz, jstring appName) { @@ -94,16 +116,25 @@ void hintActivityLaunch_native(JNIEnv* env, jobject clazz) { } const JNINativeMethod g_methods[] = { - { "isDebuggable", "()Z", reinterpret_cast<void*>(isDebuggable_native) }, - { "setDriverPathAndSphalLibraries", "(Ljava/lang/String;Ljava/lang/String;)V", reinterpret_cast<void*>(setDriverPathAndSphalLibraries_native) }, - { "setGpuStats", "(Ljava/lang/String;Ljava/lang/String;JJLjava/lang/String;I)V", reinterpret_cast<void*>(setGpuStats_native) }, - { "setInjectLayersPrSetDumpable", "()Z", reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native) }, - { "setAngleInfo", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/FileDescriptor;JJ)V", reinterpret_cast<void*>(setAngleInfo_native) }, - { "getShouldUseAngle", "(Ljava/lang/String;)Z", reinterpret_cast<void*>(shouldUseAngle_native) }, - { "setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", reinterpret_cast<void*>(setLayerPaths_native) }, - { "setDebugLayers", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayers_native) }, - { "setDebugLayersGLES", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayersGLES_native) }, - { "hintActivityLaunch", "()V", reinterpret_cast<void*>(hintActivityLaunch_native) }, + {"isDebuggable", "()Z", reinterpret_cast<void*>(isDebuggable_native)}, + {"setDriverPathAndSphalLibraries", "(Ljava/lang/String;Ljava/lang/String;)V", + reinterpret_cast<void*>(setDriverPathAndSphalLibraries_native)}, + {"setGpuStats", "(Ljava/lang/String;Ljava/lang/String;JJLjava/lang/String;I)V", + reinterpret_cast<void*>(setGpuStats_native)}, + {"setInjectLayersPrSetDumpable", "()Z", + reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)}, + {"setAngleInfo", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/io/" + "FileDescriptor;JJ)V", + reinterpret_cast<void*>(setAngleInfo_native)}, + {"getShouldUseAngle", "(Ljava/lang/String;)Z", + reinterpret_cast<void*>(shouldUseAngle_native)}, + {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", + reinterpret_cast<void*>(setLayerPaths_native)}, + {"setDebugLayers", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayers_native)}, + {"setDebugLayersGLES", "(Ljava/lang/String;)V", + reinterpret_cast<void*>(setDebugLayersGLES_native)}, + {"hintActivityLaunch", "()V", reinterpret_cast<void*>(hintActivityLaunch_native)}, }; const char* const kGraphicsEnvironmentName = "android/os/GraphicsEnvironment"; diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 9efe4b15ce7a..5de30a2471b7 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1434,7 +1434,8 @@ static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) doThrowNPE(env); return 0; } - sp<SurfaceControl> surface = SurfaceControl::readFromParcel(parcel); + sp<SurfaceControl> surface; + SurfaceControl::readFromParcel(*parcel, &surface); if (surface == nullptr) { return 0; } @@ -1462,7 +1463,7 @@ static void nativeWriteToParcel(JNIEnv* env, jclass clazz, } SurfaceControl* const self = reinterpret_cast<SurfaceControl *>(nativeObject); if (self != nullptr) { - self->writeToParcel(parcel); + self->writeToParcel(*parcel); } } diff --git a/core/jni/core_jni_helpers.h b/core/jni/core_jni_helpers.h index d629e0dae6dd..013c65faa241 100644 --- a/core/jni/core_jni_helpers.h +++ b/core/jni/core_jni_helpers.h @@ -90,6 +90,12 @@ static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className, return res; } +static inline jobject jniGetReferent(JNIEnv* env, jobject ref) { + jclass cls = FindClassOrDie(env, "java/lang/ref/Reference"); + jmethodID get = GetMethodIDOrDie(env, cls, "get", "()Ljava/lang/Object;"); + return env->CallObjectMethod(ref, get); +} + /** * Read the specified field from jobject, and convert to std::string. * If the field cannot be obtained, return defaultValue. diff --git a/core/proto/OWNERS b/core/proto/OWNERS index 4892faaceafe..542d26fa233e 100644 --- a/core/proto/OWNERS +++ b/core/proto/OWNERS @@ -14,8 +14,12 @@ per-file settings_enums.proto=tmfang@google.com # Frameworks ogunwale@google.com jjaggi@google.com +roosa@google.com per-file usagestatsservice.proto, usagestatsservice_v2.proto = mwachens@google.com +# Biometrics +kchyn@google.com + # Launcher hyunyoungs@google.com diff --git a/core/proto/android/inputmethodservice/inputmethodservice.proto b/core/proto/android/inputmethodservice/inputmethodservice.proto new file mode 100644 index 000000000000..3b4ebb5d73e7 --- /dev/null +++ b/core/proto/android/inputmethodservice/inputmethodservice.proto @@ -0,0 +1,61 @@ +/* + * 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"; + +import "frameworks/base/core/proto/android/inputmethodservice/softinputwindow.proto"; +import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto"; + +package android.inputmethodservice; + +option java_multiple_files = true; + +message InputMethodServiceProto { + optional SoftInputWindowProto soft_input_window = 1; + optional bool views_created= 2; + optional bool decor_view_visible = 3; + optional bool decor_view_was_visible = 4; + optional bool window_visible = 5; + optional bool in_show_window = 6; + optional string configuration = 7; + optional string token = 8; + optional string input_binding = 9; + optional bool input_started = 10; + optional bool input_view_started = 11; + optional bool candidates_view_started = 12; + optional .android.view.inputmethod.EditorInfoProto input_editor_info = 13; + optional bool show_input_requested = 14; + optional bool last_show_input_requested = 15; + optional bool can_pre_render = 16; + optional bool is_pre_rendered = 17; + optional int32 show_input_flags = 18; + optional int32 candidates_visibility = 19; + optional bool fullscreen_applied = 20; + optional bool is_fullscreen = 21; + optional bool extract_view_hidden = 22; + optional int32 extracted_token = 23; + optional bool is_input_view_shown = 24; + optional int32 status_icon = 25; + optional InsetsProto last_computed_insets = 26; + optional string settings_observer = 27; + + message InsetsProto { + optional int32 content_top_insets = 1; + optional int32 visible_top_insets = 2; + optional int32 touchable_insets = 3; + optional string touchable_region = 4; + } +}
\ No newline at end of file diff --git a/libs/hwui/shader/BitmapShader.cpp b/core/proto/android/inputmethodservice/softinputwindow.proto index fe653e85a021..85b7d7382805 100644 --- a/libs/hwui/shader/BitmapShader.cpp +++ b/core/proto/android/inputmethodservice/softinputwindow.proto @@ -14,18 +14,19 @@ * limitations under the License. */ -#include "BitmapShader.h" +syntax = "proto2"; -#include "SkImagePriv.h" +import "frameworks/base/core/proto/android/graphics/rect.proto"; -namespace android::uirenderer { -BitmapShader::BitmapShader(const sk_sp<SkImage>& image, const SkTileMode tileModeX, - const SkTileMode tileModeY, const SkMatrix* matrix) - : Shader(matrix), skShader(image->makeShader(tileModeX, tileModeY)) {} +package android.inputmethodservice; -sk_sp<SkShader> BitmapShader::makeSkShader() { - return skShader; -} +option java_multiple_files = true; -BitmapShader::~BitmapShader() {} -} // namespace android::uirenderer
\ No newline at end of file +message SoftInputWindowProto { + optional string name = 1; + optional int32 window_type = 2; + optional int32 gravity = 3; + optional bool takes_focus = 4; + optional .android.graphics.RectProto bounds = 5; + optional int32 window_state = 6; +}
\ No newline at end of file diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index 50912702e3d6..16a691c9c4ec 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -463,6 +463,8 @@ message GlobalSettingsProto { // Updatable Driver - List of Apps selected to use updatable prerelease driver // i.e. <pkg1>,<pkg2>,...,<pkgN> optional SettingProto updatable_driver_prerelease_opt_in_apps = 18; + + optional SettingProto angle_egl_features = 19; } optional Gpu gpu = 59; diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto new file mode 100644 index 000000000000..35aae8f92d93 --- /dev/null +++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto"; + +package android.server.inputmethod; + +option java_multiple_files = true; + +message InputMethodManagerServiceProto { + optional string cur_method_id = 1; + optional int32 cur_seq = 2; + optional string cur_client = 3; + optional string cur_focused_window_name = 4; + optional string last_ime_target_window_name = 5; + optional string cur_focused_window_soft_input_mode = 6; + optional .android.view.inputmethod.EditorInfoProto cur_attribute = 7; + optional string cur_id = 8; + optional bool show_requested = 9; + optional bool show_explicitly_requested = 10; + optional bool show_forced = 11; + optional bool input_shown = 12; + optional bool in_fullscreen_mode = 13; + optional string cur_token = 14; + optional int32 cur_token_display_id = 15; + optional bool system_ready = 16; + optional int32 last_switch_user_id = 17; + optional bool have_connection = 18; + optional bool bound_to_method = 19; + optional bool is_interactive = 20; + optional int32 back_disposition = 21; + optional int32 ime_window_visibility = 22; + optional bool show_ime_with_hard_keyboard = 23; + optional bool accessibility_requesting_no_soft_keyboard = 24; +}
\ No newline at end of file diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto index 59556c4414ce..c465233036c4 100644 --- a/core/proto/android/server/peopleservice.proto +++ b/core/proto/android/server/peopleservice.proto @@ -46,6 +46,10 @@ message ConversationInfoProto { // The notification channel id of the conversation. optional string notification_channel_id = 4 [(.android.privacy).dest = DEST_EXPLICIT]; + // The parent notification channel ID of the conversation. This is the notification channel where + // the notifications are posted before this conversation is customized by the user. + optional string parent_notification_channel_id = 8 [(.android.privacy).dest = DEST_EXPLICIT]; + // Integer representation of shortcut bit flags. optional int32 shortcut_flags = 5; @@ -54,6 +58,11 @@ message ConversationInfoProto { // The phone number of the contact. optional string contact_phone_number = 7 [(.android.privacy).dest = DEST_EXPLICIT]; + + // The timestamp of the last event in millis. + optional int64 last_event_timestamp = 9; + + // Next tag: 10 } // On disk data of events. diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index d4b226dead21..d4d8772f3f81 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -30,6 +30,10 @@ import "frameworks/base/core/proto/android/view/windowlayoutparams.proto"; import "frameworks/base/core/proto/android/privacy.proto"; import "frameworks/base/core/proto/android/typedef.proto"; +import "frameworks/base/core/proto/android/view/surfacecontrol.proto"; +import "frameworks/base/core/proto/android/view/insetssource.proto"; +import "frameworks/base/core/proto/android/view/insetssourcecontrol.proto"; + package com.android.server.wm; option java_multiple_files = true; @@ -47,6 +51,7 @@ message WindowManagerServiceDumpProto { optional int32 rotation = 7 [(.android.typedef) = "android.view.Surface.Rotation"]; optional int32 last_orientation = 8 [(.android.typedef) = "android.content.pm.ActivityInfo.ScreenOrientation"]; optional int32 focused_display_id = 9; + optional bool hard_keyboard_available = 10; } /* represents RootWindowContainer object */ @@ -195,6 +200,14 @@ message DisplayContentProto { optional .com.android.server.wm.IdentifierProto resumed_activity = 24; repeated TaskProto tasks = 25 [deprecated=true]; optional bool display_ready = 26; + + optional WindowStateProto input_method_target = 27; + optional WindowStateProto input_method_input_target = 28; + optional WindowStateProto input_method_control_target = 29; + optional WindowStateProto current_focus = 30; + optional InsetsSourceProviderProto insets_source_provider = 31; + optional ImeInsetsSourceProviderProto ime_insets_source_provider = 32; + optional bool can_show_ime = 33; } /* represents DisplayArea object */ @@ -226,6 +239,8 @@ message DisplayFramesProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional .android.graphics.RectProto stable_bounds = 1; + optional .android.graphics.RectProto dock = 2; + optional .android.graphics.RectProto current = 3; } /* represents DockedStackDividerController */ @@ -488,3 +503,30 @@ message WindowFramesProto { optional .android.graphics.RectProto stable_insets = 14; optional .android.graphics.RectProto outsets = 15; } + +message InsetsSourceProviderProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional .android.view.InsetsSourceProto source = 1; + optional .android.graphics.RectProto frame = 2; + optional .android.view.InsetsSourceControlProto fake_control = 3; + optional .android.view.InsetsSourceControlProto control = 4; + optional WindowStateProto control_target = 5; + optional WindowStateProto pending_control_target = 6; + optional WindowStateProto fake_control_target = 7; + optional .android.view.SurfaceControlProto captured_leash = 8; + optional .android.graphics.RectProto ime_overridden_frame = 9; + optional bool is_leash_ready_for_dispatching = 10; + optional bool client_visible = 11; + optional bool server_visible = 12; + optional bool seamless_rotating = 13; + optional int64 finish_seamless_rotate_frame_number = 14; + optional bool controllable = 15; +} + +message ImeInsetsSourceProviderProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional WindowStateProto ime_target_from_ime = 1; + optional bool is_ime_layout_drawn = 2; +}
\ No newline at end of file diff --git a/core/proto/android/telephony/enums.proto b/core/proto/android/telephony/enums.proto index f14e3ed1872d..b56bd2bae29a 100644 --- a/core/proto/android/telephony/enums.proto +++ b/core/proto/android/telephony/enums.proto @@ -159,3 +159,50 @@ enum SimStateEnum { */ SIM_STATE_PRESENT = 11; } + +// Format of SMS message +enum SmsFormatEnum { + /** Unknown format */ + SMS_FORMAT_UNKNOWN = 0; + /** Format compliant with 3GPP TS 23.040 */ + SMS_FORMAT_3GPP = 1; + /** Format compliant with 3GPP2 TS C.S0015-B */ + SMS_FORMAT_3GPP2 = 2; +} + +// Technology used to carry an SMS message +enum SmsTechEnum { + /** + * Unknown SMS technology used to carry the SMS. + * This value is also used for injected SMS. + */ + SMS_TECH_UNKNOWN = 0; + /** The SMS was carried over CS bearer in 3GPP network */ + SMS_TECH_CS_3GPP = 1; + /** The SMS was carried over CS bearer in 3GPP2 network */ + SMS_TECH_CS_3GPP2 = 2; + /** The SMS was carried over IMS */ + SMS_TECH_IMS = 3; +} + +// Types of SMS message +enum SmsTypeEnum { + /** Normal type. */ + SMS_TYPE_NORMAL = 0; + /** SMS-PP (point-to-point). */ + SMS_TYPE_SMS_PP = 1; + /** Voicemail indication. */ + SMS_TYPE_VOICEMAIL_INDICATION = 2; + /** Type 0 message (3GPP TS 23.040 9.2.3.9). */ + SMS_TYPE_ZERO = 3; + /** WAP-PUSH message. */ + SMS_TYPE_WAP_PUSH = 4; +} + +// SMS errors +enum SmsIncomingErrorEnum { + SMS_SUCCESS = 0; + SMS_ERROR_GENERIC = 1; + SMS_ERROR_NO_MEMORY = 2; + SMS_ERROR_NOT_SUPPORTED = 3; +} diff --git a/core/proto/android/view/imeinsetssourceconsumer.proto b/core/proto/android/view/imeinsetssourceconsumer.proto index 680916345a31..5bee81bdc7cd 100644 --- a/core/proto/android/view/imeinsetssourceconsumer.proto +++ b/core/proto/android/view/imeinsetssourceconsumer.proto @@ -17,6 +17,7 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto"; +import "frameworks/base/core/proto/android/view/insetssourceconsumer.proto"; package android.view; @@ -26,6 +27,7 @@ option java_multiple_files = true; * Represents a {@link android.view.ImeInsetsSourceConsumer} object. */ message ImeInsetsSourceConsumerProto { - optional .android.view.inputmethod.EditorInfoProto focused_editor = 1; - optional bool is_requested_visible_awaiting_control = 2; + optional InsetsSourceConsumerProto insets_source_consumer = 1; + optional .android.view.inputmethod.EditorInfoProto focused_editor = 2; + optional bool is_requested_visible_awaiting_control = 3; }
\ No newline at end of file diff --git a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto index 732213966014..2729e82768c3 100644 --- a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto +++ b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto @@ -22,11 +22,13 @@ package android.view.inputmethod; import "frameworks/base/core/proto/android/view/inputmethod/inputmethodmanager.proto"; import "frameworks/base/core/proto/android/view/viewrootimpl.proto"; import "frameworks/base/core/proto/android/view/insetscontroller.proto"; -import "frameworks/base/core/proto/android/view/insetssourceconsumer.proto"; import "frameworks/base/core/proto/android/view/imeinsetssourceconsumer.proto"; import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto"; import "frameworks/base/core/proto/android/view/imefocuscontroller.proto"; +import "frameworks/base/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto"; +import "frameworks/base/core/proto/android/inputmethodservice/inputmethodservice.proto"; + /** * Represents a file full of input method editor trace entries. * Encoded, it should start with 0x9 0x49 0x4d 0x45 0x54 0x52 0x41 0x43 0x45 (.IMETRACE), such @@ -54,14 +56,23 @@ message InputMethodEditorProto { /* required: elapsed realtime in nanos since boot of when this entry was logged */ optional fixed64 elapsed_realtime_nanos = 1; - optional ClientSideProto client_side_dump = 2; + optional ClientsProto clients = 2; + optional .android.inputmethodservice.InputMethodServiceProto input_method_service = 3; + optional .android.server.inputmethod.InputMethodManagerServiceProto input_method_manager_service = 4; + + // this wrapper helps to simplify the dumping logic + message ClientsProto { + repeated ClientSideProto client = 1; + } + /* todo: extract as a separate message to allow other dumps to use this as their client side + proto */ /* groups together the dump from ime related client side classes */ message ClientSideProto { - optional InputMethodManagerProto input_method_manager = 1; - optional ViewRootImplProto view_root_impl = 2; - optional InsetsControllerProto insets_controller = 3; - optional InsetsSourceConsumerProto insets_source_consumer = 4; + optional int32 display_id = 1; + optional InputMethodManagerProto input_method_manager = 2; + optional ViewRootImplProto view_root_impl = 3; + optional InsetsControllerProto insets_controller = 4; optional ImeInsetsSourceConsumerProto ime_insets_source_consumer = 5; optional EditorInfoProto editor_info = 6; optional ImeFocusControllerProto ime_focus_controller = 7; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 34543f13c166..7247e4fd3311 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1244,8 +1244,19 @@ android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_recordAudio" android:description="@string/permdesc_recordAudio" + android:backgroundPermission="android.permission.RECORD_BACKGROUND_AUDIO" android:protectionLevel="dangerous|instant" /> + <!-- Allows an application to record audio while in the background. + <p>Protection level: dangerous + --> + <permission android:name="android.permission.RECORD_BACKGROUND_AUDIO" + android:permissionGroup="android.permission-group.UNDEFINED" + android:label="@string/permlab_recordBackgroundAudio" + android:description="@string/permdesc_recordBackgroundAudio" + android:permissionFlags="hardRestricted|installerExemptIgnored" + android:protectionLevel="dangerous" /> + <!-- ====================================================================== --> <!-- Permissions for activity recognition --> <!-- ====================================================================== --> @@ -1313,8 +1324,19 @@ android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_camera" android:description="@string/permdesc_camera" + android:backgroundPermission="android.permission.BACKGROUND_CAMERA" android:protectionLevel="dangerous|instant" /> + <!-- Required to be able to access the camera device in the background. + <p>Protection level: dangerous + --> + <permission android:name="android.permission.BACKGROUND_CAMERA" + android:permissionGroup="android.permission-group.UNDEFINED" + android:label="@string/permlab_backgroundCamera" + android:description="@string/permdesc_backgroundCamera" + android:permissionFlags="hardRestricted|installerExemptIgnored" + android:protectionLevel="dangerous" /> + <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access system only camera devices. <p>Protection level: system|signature @@ -2687,6 +2709,14 @@ <permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE" android:protectionLevel="signature" /> + <!-- Allows applications like settings to manage configuration associated with automatic time + and time zone detection. + <p>Not for use by third-party applications. + @hide + --> + <permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" + android:protectionLevel="signature|privileged" /> + <!-- ==================================================== --> <!-- Permissions related to changing status bar --> <!-- ==================================================== --> @@ -3546,6 +3576,13 @@ android:protectionLevel="signature" /> <uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" /> + <!-- This permission is required by Media Resource Observer Service when + accessing its registerObserver Api. + <p>Protection level: signature|privileged + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" + android:protectionLevel="signature|privileged" /> <!-- Must be required by a {@link android.media.routing.MediaRouteService} to ensure that only the system can interact with it. @@ -4322,6 +4359,10 @@ <permission android:name="android.permission.WRITE_DREAM_STATE" android:protectionLevel="signature|privileged" /> + <!-- @hide Allows applications to read whether ambient display is suppressed. --> + <permission android:name="android.permission.READ_DREAM_SUPPRESSION" + android:protectionLevel="signature" /> + <!-- @SystemApi Allow an application to read and write the cache partition. @hide --> <permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index b54dfc011735..e6b7c68e1c1d 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"verander jou klankinstellings"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Laat die program toe om globale klankinstellings soos volume en watter luidspreker vir uitvoer gebruik word, te verander."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"neem klank op"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Hierdie program kan enige tyd oudio met die mikrofoon opneem."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"stuur bevele na die SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Laat die program toe om bevele na die SIM te stuur. Dit is baie gevaarlik."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"herken fisieke aktiwiteit"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Hierdie program kan jou fisieke aktiwiteit herken."</string> <string name="permlab_camera" msgid="6320282492904119413">"neem foto\'s en video\'s"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Hierdie program kan enige tyd met die kamera foto\'s neem en video\'s opneem."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Gee \'n program of diens toegang tot stelselkameras om foto\'s en video\'s te neem"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Hierdie bevoorregte of stelselprogram kan enige tyd met \'n stelselkamera foto\'s neem en video\'s opneem. Vereis dat die program ook die android.permission.CAMERA-toestemming het"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Laat \'n program of diens toe om terugbeloproepe te ontvang oor kameratoestelle wat oopgemaak of toegemaak word."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Verhoog volume bo aanbevole vlak?\n\nOm lang tydperke teen hoë volume te luister, kan jou gehoor beskadig."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gebruik toeganklikheidkortpad?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Wanneer die kortpad aan is, sal \'n toeganklikheidkenmerk begin word as albei volumeknoppies 3 sekondes lank gedruk word."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Skakel toeganklikheidkenmerke aan?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Skakel kortpad vir toeganklikheidskenmerke aan?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"As jy albei volumesleutels vir \'n paar sekondes hou, skakel dit toeganklikheidkenmerke aan. Dit kan verander hoe jou toestel werk.\n\nHuidige kenmerke:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nJy kan geselekteerde kenmerke in Instellings en Toeganklikheid verander."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Skakel <xliff:g id="SERVICE">%1$s</xliff:g> aan?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Skakel <xliff:g id="SERVICE">%1$s</xliff:g>-kortpad aan?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"As jy albei volumesleutels vir \'n paar sekondes hou, skakel dit <xliff:g id="SERVICE">%1$s</xliff:g>, \'n toeganklikheidkenmerk, aan. Dit kan verander hoe jou toestel werk.\n\nJy kan hierdie kortpad na \'n ander kenmerk in Instellings en Toeganklikheid verander."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Skakel aan"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Moenie aanskakel nie"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 668ce4232f76..9750a0c94f0c 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"የድምፅ ቅንብሮችን ለውጥ"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"መተግበሪያው አንደ የድምጽ መጠን እና ለውጽአት የትኛውን የድምጽ ማጉያ ጥቅም ላይ እንደዋለ የመሳሰሉ ሁለንተናዊ የድምጽ ቅንብሮችን እንዲያስተካክል ይፈቅድለታል።"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ኦዲዮ ይቅዱ"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"ይህ መተግበሪያ በማናቸውም ጊዜ ማይክራፎኑን በመጠቀም ኦዲዮን መቅዳት ይችላል።"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"ወደ ሲሙ ትዕዛዞችን መላክ"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"መተግበሪያው ትዕዛዞችን ወደ ሲሙ እንዲልክ ያስችለዋል። ይሄ በጣማ አደገኛ ነው።"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"አካላዊ እንቅስቃሴን ለይቶ ማወቅ"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"ይህ መተግበሪያ አካላዊ እንቅስቃሴዎን ለይቶ ሊያውቅ ይችላል።"</string> <string name="permlab_camera" msgid="6320282492904119413">"ፎቶዎች እና ቪዲዮዎች ያንሱ"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"ይህ መተግበሪያ በማናቸውም ጊዜ ካሜራውን በመጠቀም ፎቶ ሊያነሳ እና ቪዲዮዎችን ሊቀርጽ ይችላል።"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"ሥዕሎችን ለማንሣት እና ቪዲዮዎችን ለመቅረጽ እንዲችሉ ወደ ሥርዓት ካሜራዎች ለመተግበሪያ ወይም ለአገልግሎት መዳረሻ ይፍቀዱ"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"ይህ ልዩ ፈቃድ ያለው የሥርዓት መተግበሪያ በማንኛውም ጊዜ የሥርዓት ካሜራን በመጠቀም ሥዕሎችን ማንሣት እና ቪዲዮ መቅረጽ ይችላል። የandroid.permission.CAMERA ፈቃዱ በመተግበሪያውም ጭምር እንዲያዝ ያስፈልገዋል።"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"አንድ መተግበሪያ ወይም አገልግሎት እየተከፈቱ ወይም እየተዘጉ ስላሉ የካሜራ መሣሪያዎች መልሶ ጥሪዎችን እንዲቀበል ይፍቀዱ።"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ድምጹ ከሚመከረው መጠን በላይ ከፍ ይበል?\n\nበከፍተኛ ድምጽ ለረጅም ጊዜ ማዳመጥ ጆሮዎን ሊጎዳው ይችላል።"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"የተደራሽነት አቋራጭ ጥቅም ላይ ይዋል?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"አቋራጩ ሲበራ ሁለቱንም የድምጽ አዝራሮች ለ3 ሰከንዶች ተጭኖ መቆየት የተደራሽነት ባህሪን ያስጀምረዋል።"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"የተደራሽነት ባሕሪያት ይብሩ?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"የተደራሽነት ባህሪዎች አቋራጭ ይብራ?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ሁለቱንም የድምፅ ቁልፎች ወደ ታች ለጥቂት ሰከንዶች መያዝ የተደራሽነት ባሕሪያትን ያበራል። ይህ የእርስዎ መሣሪያ እንዴት እንደሚሠራ ሊለውጥ ይችላል።\n\nየአሁን ባሕሪያት፦\n<xliff:g id="SERVICE">%1$s</xliff:g>\nበቅንብሮች > ተደራሽነት ውስጥ የተመረጡትን ባሕሪያት መለወጥ ይችላሉ።"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ይብራ?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"የ<xliff:g id="SERVICE">%1$s</xliff:g> አቋራጭ ይብራ?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ሁለቱንም የድምፅ ቁልፎች ወደ ታች ለጥቂት ሰከንዶች መያዝ የተደራሽነት ባሕሪያትን <xliff:g id="SERVICE">%1$s</xliff:g> ያበራል። ይህ የእርስዎ መሣሪያ እንዴት እንደሚሠራ ሊለውጥ ይችላል።\n\nበቅንብሮች > ተደራሽነት ውስጥ ወደ ሌላ ባሕሪ ይህን አቋራጭ መለወጥ ይችላሉ።"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"አብራ"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"አታብራ"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index fdb351ca52a2..e672de31f13b 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -444,13 +444,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"تغيير إعداداتك الصوتية"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"للسماح للتطبيق بتعديل إعدادات الصوت العامة مثل مستوى الصوت وأي السماعات يتم استخدامها للاستماع."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"تسجيل الصوت"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"يمكن لهذا التطبيق تسجيل الصوت باستخدام الميكروفون في أي وقت."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"إرسال أوامر إلى شريحة SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"السماح للتطبيق بإرسال أوامر إلى شريحة SIM. وهذا أمر بالغ الخطورة."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"التعرّف على النشاط البدني"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"يمكن لهذا التطبيق التعرّف على نشاطك البدني."</string> <string name="permlab_camera" msgid="6320282492904119413">"التقاط صور وفيديوهات"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"يمكن لهذا التطبيق التقاط صور وتسجيل فيديوهات باستخدام الكاميرا في أي وقت."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"السماح لتطبيق أو خدمة بالوصول إلى كاميرات النظام لالتقاط صور وتسجيل فيديوهات"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"إنّ تطبيق النظام هذا، أو التطبيق المزوّد بأذونات مميّزة، يمكنه التقاط صور وتسجيل فيديوهات باستخدام كاميرا النظام في أي وقت. ويجب أن يحصل التطبيق أيضًا على الإذن android.permission.CAMERA."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"يسمح الإذن لتطبيق أو خدمة بتلقّي استدعاءات عما إذا كانت أجهزة الكاميرات مفتوحة أو مغلقة."</string> @@ -1710,10 +1720,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"هل تريد رفع مستوى الصوت فوق المستوى الموصى به؟\n\nقد يضر سماع صوت عالٍ لفترات طويلة بسمعك."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"هل تريد استخدام اختصار \"سهولة الاستخدام\"؟"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"عند تفعيل الاختصار، يؤدي الضغط على زرّي التحكّم في مستوى الصوت معًا لمدة 3 ثوانٍ إلى تفعيل إحدى ميزات إمكانية الوصول."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"هل تريد تفعيل ميزات إمكانية الوصول؟"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"هل تريد تفعيل الاختصار لميزات إمكانية الوصول؟"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"يؤدي الضغط مع الاستمرار على كلا مفتاحَي التحكّم في مستوى الصوت لبضع ثوانٍ إلى تفعيل ميزات إمكانية الوصول. قد يؤدي هذا الإجراء إلى تغيير طريقة عمل جهازك.\n\nالميزات الحالية:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nيمكنك تغيير الميزات المحددة في الإعدادات > إمكانية الوصول."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"هل تريد تفعيل <xliff:g id="SERVICE">%1$s</xliff:g>؟"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"هل تريد تفعيل اختصار <xliff:g id="SERVICE">%1$s</xliff:g>؟"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"يؤدي الضغط مع الاستمرار لبضع ثوانٍ على كلا مفتاحَي التحكّم في مستوى الصوت إلى تفعيل <xliff:g id="SERVICE">%1$s</xliff:g> وهي إحدى ميزات إمكانية الوصول. يمكن أن يؤدي هذا الإجراء إلى تغيير كيفية عمل جهازك.\n\nيمكنك تغيير هذا الاختصار لاستخدامه مع ميزة أخرى في الإعدادات > أدوات تمكين الوصول."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"تفعيل"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"عدم التفعيل"</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index 93ce15fc73b4..b3e1680f2a40 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"আপোনাৰ অডিঅ\' ছেটিংসমূহ সলনি কৰক"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"এপটোক ভলিউমৰ দৰে গ্ল\'বেল অডিঅ\' ছেটিংসমূহ যাৰ স্পীকাৰক আউটপুটৰ বাবে ব্যৱহাৰ হয় তাক সলনি কৰিবলৈ অনুমতি দিয়ে৷"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"অডিঅ\' ৰেকর্ড কৰক"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"এই এপটোৱে যিকোনো সময়তে মাইক্ৰ\'ফ\'ন ব্যৱহাৰ কৰি অডিঅ\' ৰেকৰ্ড কৰিব পাৰে।"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"ছিমলৈ নিৰ্দেশ পঠিয়াব পাৰে"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"ছিমলৈ নিৰ্দেশসমূহ প্ৰেৰণ কৰিবলৈ এপক অনুমতি দিয়ে। ই অতি ক্ষতিকাৰক।"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"শাৰীৰিক কাৰ্যকলাপ চিনাক্ত কৰক"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"এই এপটোৱে আপোনাৰ শাৰীৰিক কাৰ্যকলাপ চিনাক্ত কৰিব পাৰে।"</string> <string name="permlab_camera" msgid="6320282492904119413">"ফট\' তোলা আৰু ভিডিঅ\' ৰেকৰ্ড কৰা"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"এই এপে যিকোনো সময়তে কেমেৰা ব্যৱহাৰ কৰি ফট\' তুলিব আৰু ভিডিঅ\' ৰেকর্ড কৰিব পাৰে।"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"ফট’ উঠাবলৈ আৰু ভিডিঅ’ ৰেকৰ্ড কৰিবলৈ এটা এপ্লিকেশ্বন অথবা সেৱাক ছিষ্টেম কেমেৰাসমূহ এক্সেছ কৰিবলৈ দিয়ক"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"এই বিশেষাধিকাৰ প্ৰাপ্ত অথবা ছিষ্টেম এপ্টোৱে এটা ছিষ্টেম কেমেৰা ব্যৱহাৰ কৰি যিকোনো সময়তে ফট’ উঠাব পাৰে আৰু ভিডিঅ’ ৰেকৰ্ড কৰিব পাৰে। লগতে এপ্টোৰো android.permission.CAMERAৰ অনুমতি থকাটো প্ৰয়োজনীয়"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"কোনো এপ্লিকেশ্বন অথবা সেৱাক কেমেৰা ডিভাইচসমূহ খোলা অথবা বন্ধ কৰাৰ বিষয়ে কলবেকসমূহ গ্ৰহণ কৰিবলৈ অনুমতি দিয়ক।"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"অনুমোদিত স্তৰতকৈ ওপৰলৈ ভলিউম বঢ়াব নেকি?\n\nদীৰ্ঘ সময়ৰ বাবে উচ্চ ভলিউমত শুনাৰ ফলত শ্ৰৱণ ক্ষমতাৰ ক্ষতি হ\'ব পাৰে।"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"দিব্যাংগসকলৰ সুবিধাৰ শ্বৰ্টকাট ব্যৱহাৰ কৰেনে?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"শ্বৰ্টকাটটো অন হৈ থকাৰ সময়ত দুয়োটা ভলিউম বুটাম ৩ ছেকেণ্ডৰ বাবে হেঁচি ধৰি ৰাখিলে এটা সাধ্য সুবিধা আৰম্ভ হ’ব।"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"সাধ্য-সুবিধাসমূহ অন কৰিবনে?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"সাধ্য সুবিধাসমূহৰ বাবে শ্বৰ্টকাট অন কৰিবনে?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"দুয়োটা ভলিউম কী কিছুসময়ৰ বাবে ধৰি থাকিলে সাধ্য-সুবিধাসমূহ অন কৰে। এইটোৱে আপোনাৰ ডিভাইচটোৱে কাম কৰাৰ ধৰণ সলনি কৰিব পাৰে।\n\nবর্তমানৰ সুবিধাসমূহ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nআপুনি ছেটিংসমূহ > সাধ্য-সুবিধাত কিছুমান নিৰ্দিষ্ট সুবিধা সলনি কৰিব পাৰে।"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> অন কৰিবনে?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g>ৰ শ্বৰ্টকাট অন কৰিবনে?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"দুয়োটা ভলিউম কী কিছুসময়ৰ বাবে ধৰি থাকিলে এটা সাধ্য- সুবিধা <xliff:g id="SERVICE">%1$s</xliff:g> অন কৰে। এইটোৱে আপোনাৰ ডিভাইচটোৱে কাম কৰাৰ ধৰণ সলনি কৰিব পাৰে।\n\nআপুনি ছেটিংসমূহ > সাধ্য-সুবিধাসমূহত এই শ্বৰ্টকাটটো অন্য এটা সুবিধালৈ সলনি কৰিব পাৰে।"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"অন কৰক"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"অন নকৰিব"</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index 01b815b43202..de399df09e61 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"audio ayarlarınızı dəyişir"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Tətbiqə səs və hansı spikerin çıxış üçün istifadə olunduğu kimi qlobal səs ayarlarını dəyişdirməyə imkan verir."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"səs yaz"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Bu tətbiq istədiyiniz zaman mikrofonu istifadə edərək audio qeyd edə bilər."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"əmrləri SIM\'ə göndərin"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Tətbiqə SIM-ə əmrlər göndərməyə imkan verir. Bu, çox təhlükəlidir."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"fiziki fəaliyyəti tanıyın"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Bu tətbiq fiziki fəaliyyətinizi tanıya bilər."</string> <string name="permlab_camera" msgid="6320282492904119413">"şəkil və video çəkmək"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Bu tətbiq istədiyiniz zaman kameranı istifadə edərək şəkil çəkə və video qeydə ala bilər."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Şəkil və video çəkmək üçün tətbiq və ya xidmətlərin sistem kameralarına girişinə icazə verin"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Bu icazəli və ya sistem tətbiqi istənilən vaxt sistem kamerasından istifadə edərək şəkil və videolar çəkə bilər. android.permission.CAMERA icazəsinin də tətbiq tərəfindən saxlanılmasını tələb edir"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Tətbiqə və ya xidmətə kamera cihazlarının açılması və ya bağlanması haqqında geri zənglər qəbul etməyə icazə verin."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Səsin həcmi tövsiyə olunan səviyyədən artıq olsun?\n\nYüksək səsi uzun zaman dinləmək eşitmə qabiliyyətinizə zərər vura bilər."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Əlçatımlılıq Qısayolu istifadə edilsin?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Qısayol aktiv olduqda, hər iki səs düyməsinə 3 saniyə basıb saxlamaqla əlçatımlılıq funksiyası başladılacaq."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Əlçatımlılıq funksiyaları aktiv edilsin?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Əlçatımlılıq funksiyaları üçün qısayol aktiv edilsin?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hər iki səs səviyyəsi düyməsinə bir neçə saniyə basıb saxladıqda əlçatımlılıq funksiyaları aktiv olur. Bu, cihazınızın işləmə qaydasını dəyişə bilər.\n\nCari funksiyalar:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nAyarlar və Əlçatımlılıq bölməsində seçilmiş funksiyaları dəyişə bilərsiniz."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> aktiv edilsin?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> qısayolu aktiv edilsin?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Hər iki səs səviyyəsi düyməsinə bir neçə saniyə basıb saxladıqda əlçatımlılıq funksiyası olan <xliff:g id="SERVICE">%1$s</xliff:g> aktiv olur. Bu, cihazınızın işləmə qaydasını dəyişə bilər.\n\nAyarlar və Əlçatımlılıq bölməsində bu qısayolu başqa bir funksiyata dəyişə bilərsiniz."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktiv edin"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Aktiv etməyin"</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index e2fc8f1f7862..928ade146c31 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -435,13 +435,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"promena audio podešavanja"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Dozvoljava aplikaciji da menja globalna audio podešavanja kao što su jačina zvuka i izbor zvučnika koji se koristi kao izlaz."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"snimanje audio zapisa"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ova aplikacija može da snima zvuk pomoću mikrofona u bilo kom trenutku."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"slanje komandi na SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućava aplikaciji da šalje komande SIM kartici. To je veoma opasno."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje fizičkih aktivnosti"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ova aplikacija može da prepozna fizičke aktivnosti."</string> <string name="permlab_camera" msgid="6320282492904119413">"snimanje fotografija i video snimaka"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Ova aplikacija može da snima fotografije i video snimke pomoću kamere u bilo kom trenutku."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Dozvolite nekoj aplikaciji ili usluzi da pristupa kamerama sistema da bi snimala slike i video snimke"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ova privilegovana sistemska aplikacija može da snima slike i video snimke pomoću kamere sistema u bilo kom trenutku. Aplikacija treba da ima i dozvolu android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Dozvolite aplikaciji ili usluzi da dobija povratne pozive o otvaranju ili zatvaranju uređaja sa kamerom."</string> @@ -1644,10 +1654,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite da pojačate zvuk iznad preporučenog nivoa?\n\nSlušanje glasne muzike duže vreme može da vam ošteti sluh."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li da koristite prečicu za pristupačnost?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kada je prečica uključena, pritisnite oba dugmeta za jačinu zvuka da biste pokrenuli funkciju pristupačnosti."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Želite li da uključite funkcije pristupačnosti?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Želite da uključite prečicu za funkcije pristupačnosti?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ako zadržite oba tastera za jačinu zvuka par sekundi, uključiće se funkcije pristupačnosti. To može da promeni način rada uređaja.\n\nPostojeće funkcije:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nMožete da promenite izabrane funkcije u odeljku Podešavanja > Pristupačnost."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Želite li da uključite uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Želite da uključite prečicu za uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ako zadržite oba tastera za jačinu zvuka par sekundi, uključuje se <xliff:g id="SERVICE">%1$s</xliff:g>, funkcija pristupačnosti. To može da promeni način rada uređaja.\n\nMožete da promenite funkciju na koju se odnosi ova prečica u odeljku Podešavanja > Pristupačnost."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Uključi"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ne uključuj"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 45008f2458bb..e31c4b73de67 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -438,13 +438,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"змяняць налады аудыё"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Дазваляе прыкладанням змяняць глабальныя налады гуку, такія як моц і тое, што дынамік выкарыстоўваецца для выхаду."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"запіс аўдыя"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Гэта праграма можа у любы час запісваць аўдыя, выкарыстоўваючы мікрафон."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"адпраўляць каманды на SIM-карту"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Дазваляе праграме адпраўляць каманды SIM-карце. Гэта вельмі небяспечна."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"распазнаваць фізічную актыўнасць"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Гэта праграма можа распазнаваць фізічную актыўнасць."</string> <string name="permlab_camera" msgid="6320282492904119413">"рабіць фатаграфіі і відэа"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Гэта праграма можа рабіць фота і запісваць відэа з дапамогай камеры ў любы час."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Дазволіць праграме або сэрвісу атрымліваць доступ да сістэмных камер, каб здымаць фота і запісваць відэа"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Гэта прыярытэтная ці сістэмная праграма можа здымаць фота і запісваць відэа з дапамогай сістэмнай камеры. Праграме таксама патрэбны дазвол android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Дазволіць праграме ці сэрвісу атрымліваць зваротныя выклікі наконт адкрыцця ці закрыцця прылад камеры."</string> @@ -970,7 +980,7 @@ <string name="permlab_addVoicemail" msgid="4770245808840814471">"дадаць галасавое паведамленне"</string> <string name="permdesc_addVoicemail" msgid="5470312139820074324">"Дазваляе прыкладанням дадаваць паведамленні ў вашу скрыню галасавой пошты."</string> <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"змяніць дазволы геапазіцыянавання для браўзэра"</string> - <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Дазваляе прыкладанням змяняць дазволы геалакацыі браўзэра. Шкоднасныя прыкладанні могуць выкарыстоўваць гэта, каб дазваляць адпраўку інфармацыі аб месцазнаходжанні выпадковым вэб-сайтам."</string> + <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Дазваляе праграме змяняць дазволы геалакацыі браўзера. Шкодныя праграмы могуць выкарыстоўваць гэта, каб адпраўляць даныя аб месцазнаходжанні на любыя вэб-сайты."</string> <string name="save_password_message" msgid="2146409467245462965">"Вы хочаце, каб браўзэр запомніў гэты пароль?"</string> <string name="save_password_notnow" msgid="2878327088951240061">"Не зараз"</string> <string name="save_password_remember" msgid="6490888932657708341">"Запомніць"</string> @@ -1345,8 +1355,8 @@ <string name="usb_tether_notification_title" msgid="8828527870612663771">"Рэжым USB-мадэма"</string> <string name="usb_midi_notification_title" msgid="7404506788950595557">"MIDI праз USB"</string> <string name="usb_accessory_notification_title" msgid="1385394660861956980">"USB-прылада падключана"</string> - <string name="usb_notification_message" msgid="4715163067192110676">"Дакраніцеся, каб атрымаць іншыя параметры."</string> - <string name="usb_power_notification_message" msgid="7284765627437897702">"Зарадка падключанай прылады. Націсніце, каб убачыць іншыя параметры."</string> + <string name="usb_notification_message" msgid="4715163067192110676">"Дакраніцеся, каб убачыць іншыя параметры."</string> + <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> @@ -1666,10 +1676,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Павялiчыць гук вышэй рэкамендаванага ўзроўню?\n\nДоўгае праслухоўванне музыкi на вялiкай гучнасцi можа пашкодзiць ваш слых."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Выкарыстоўваць камбінацыю хуткага доступу для спецыяльных магчымасцей?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Калі хуткі доступ уключаны, вы можаце націснуць абедзве кнопкі гучнасці і ўтрымліваць іх 3 секунды, каб запусціць функцыю спецыяльных магчымасцей."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Уключыць спецыяльныя магчымасці?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Уключыць хуткі доступ да спецыяльных магчымасцей?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Утрымліванне націснутымі абедзвюх клавіш гучнасці на працягу некалькіх секунд уключае спецыяльныя магчымасці. У выніку ваша прылада можа пачаць працаваць па-іншаму.\n\nБягучыя функцыі:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nВыбраныя функцыі можна змяніць у меню \"Налады > Спецыяльныя магчымасці\"."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Уключыць службу \"<xliff:g id="SERVICE">%1$s</xliff:g>\"?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Уключыць хуткі доступ да сэрвісу \"<xliff:g id="SERVICE">%1$s</xliff:g>\"?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Утрымліванне націснутымі абедзвюх клавіш гучнасці на працягу некалькіх секунд уключае службу \"<xliff:g id="SERVICE">%1$s</xliff:g>\", якая з\'яўляецца спецыяльнай магчымасцю. У выніку ваша прылада можа пачаць працаваць па-іншаму.\n\nВы можаце задаць гэта спалучэнне клавіш для іншай функцыі ў меню \"Налады > Спецыяльныя магчымасці\"."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Уключыць"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Не ўключаць"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 0c2c0225eab9..5938fd999f90 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"промяна на настройките ви за звука"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Разрешава на приложението да променя глобалните настройки за звука, като например силата и това, кой високоговорител се използва за изход."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"записва звук"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Това приложение може по всяко време да записва звук посредством микрофона."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"изпращане на команди до SIM картата"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Разрешава на приложението да изпраща команди до SIM картата. Това е много опасно."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"разпознаване на физическата активност"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Това приложение може да разпознава физическата ви активност."</string> <string name="permlab_camera" msgid="6320282492904119413">"правене на снимки и видеоклипове"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Това приложение може по всяко време да прави снимки и да записва видео посредством камерата."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Разрешаване на достъп на приложение или услуга до системните камери с цел правене на снимки и видеоклипове"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Това привилегировано или системно приложение може по всяко време да прави снимки и да записва видео посредством системна камера. Необходимо е също на приложението да бъде дадено разрешението android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Разрешаване на приложение или услуга да получават обратни повиквания за отварянето или затварянето на снимачни устройства."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Да се увеличи ли силата на звука над препоръчителното ниво?\n\nПродължителното слушане при висока сила на звука може да увреди слуха ви."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Искате ли да използвате пряк път към функцията за достъпност?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Когато прекият път е включен, можете да стартирате дадена функция за достъпност, като натиснете двата бутона за силата на звука и ги задържите за 3 секунди."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Включване на функциите за достъпност?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Искате ли да включите прекия път за функциите за достъпност?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Натиснете двата бутона за силата на звука и ги задръжте за няколко секунди, за да включите функциите за достъпност. Това може да промени начина, по който работи устройството ви.\n\nТекущи функции:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nМожете да промените избраните функции от „Настройки“ > „Достъпност“."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Да се включи ли <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Искате ли да включите прекия път за <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Натиснете двата бутона за силата на звука и ги задръжте за няколко секунди, за да включите функцията за достъпност <xliff:g id="SERVICE">%1$s</xliff:g>. Това може да промени начина, по който работи устройството ви.\n\nМожете да зададете друга функция за този пряк път от „Настройки“ > „Достъпност“."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Включване"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Без включване"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index b10d8dcad74f..3567d794a590 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"আপনার অডিও সেটিংস পরিবর্তন করে"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ভলিউম এবং যেখানে স্পিকার আউটপুট হিসাবে ব্যবহৃত হয় সেই সব ক্ষেত্রে গ্লোবাল অডিও সেটিংসের সংশোধন করতে অ্যাপ্লিকেশনটিকে মঞ্জুর করে৷"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"অডিও রেকর্ড"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"এই অ্যাপটি মাইক্রোফোন ব্যবহার করে যে কোনো সময় অডিও রেকর্ড করতে পারে৷"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"সিম এ আদেশগুলি পাঠান"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"অ্যাপ্লিকেশানটিকে সিম কার্ডে কমান্ডগুলি পাঠানোর অনুমতি দেয়৷ এটি খুবই বিপজ্জনক৷"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"শারীরিক অ্যাক্টিভিটি শনাক্ত করুন"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"এই অ্যাপ আপনার শারীরিক অ্যাক্টিভিটি শনাক্ত করতে পারবে।"</string> <string name="permlab_camera" msgid="6320282492904119413">"ছবি এবং ভিডিও তোলে"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"এই অ্যাপটি যে কোনো সময় ক্যামেরা ব্যবহার করে ছবি তুলতে বা ভিডিও রেকর্ড করতে পারে৷"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"সিস্টেম ক্যামেরা ব্যবহার করে ফটো এবং ভিডিও নেওয়ার জন্য অ্যাপ্লিকেশন বা পরিষেবা অ্যাক্সেসের অনুমতি দিন"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"এই প্রিভিলেজ বা সিস্টেম অ্যাপ যেকোনও সময় সিস্টেম ক্যামেরা ব্যবহার করে ছবি তুলতে ও ভিডিও রেকর্ড করতে পারে। এই অ্যাপকে android.permission.CAMERA অনুমতি দিতে হবে"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"কোনও অ্যাপ্লিকেশন বা পরিষেবাকে ক্যামেরা ডিভাইসগুলি খোলা বা বন্ধ হওয়া সম্পর্কে কলব্যাকগুলি গ্রহণ করার অনুমতি দিন।"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"প্রস্তাবিত স্তরের চেয়ে বেশি উঁচুতে ভলিউম বাড়াবেন?\n\nউঁচু ভলিউমে বেশি সময় ধরে কিছু শুনলে আপনার শ্রবনশক্তির ক্ষতি হতে পারে।"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"অ্যাক্সেসযোগ্যতা শর্টকাট ব্যবহার করবেন?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"শর্টকাট চালু করা থাকাকালীন দুটি ভলিউম বোতাম একসাথে ৩ সেকেন্ড টিপে ধরে রাখলে একটি অ্যাকসেসিবিলিটি ফিচার চালু হবে।"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"অ্যাক্সেসিবিলিটি ফিচার চালু করতে চান?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"অ্যাক্সেসিবিলিটি ফিচারের শর্টকাট বন্ধ করতে চান?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"উভয় ভলিউম কী কয়েক সেকেন্ড ধরে থাকলে অ্যাক্সেসিবিলিটি ফিচার চালু হয়ে যাবে। এর ফলে, আপনার ডিভাইস কীভাবে কাজ করবে সেটিতে পরিবর্তন হতে পারে।\n\nবর্তমান ফিচার:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nসেটিংস > অ্যাক্সেসিবিলিটি বিকল্প থেকে আপনি বাছাই করা ফিচার পরিবর্তন করতে পারবেন।"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> চালু করতে চান?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> শর্টকাট চালু করতে চান?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"উভয় ভলিউম কী কয়েক সেকেন্ড ধরে থাকলে <xliff:g id="SERVICE">%1$s</xliff:g> চালু হয়ে যাবে। এটি একটি অ্যাক্সেসিবিলিটি ফিচার। এর ফলে, আপনার ডিভাইস কীভাবে কাজ করবে সেটিতে পরিবর্তন হতে পারে।\n\nসেটিংস > অ্যাক্সেসিবিলিটি থেকে আপনি এই শর্টকাট পরিবর্তন করতে পারবেন।"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"চালু করুন"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"চালু করবেন না"</string> @@ -1829,8 +1839,7 @@ <item quantity="one">%d ঘন্টার জন্য</item> <item quantity="other">%d ঘন্টার জন্য</item> </plurals> - <!-- no translation found for zen_mode_until_next_day (1403042784161725038) --> - <skip /> + <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> পর্যন্ত"</string> <string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> পর্যন্ত"</string> <string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> পর্যন্ত (পরবর্তী অ্যালার্ম)"</string> <string name="zen_mode_forever" msgid="740585666364912448">"যতক্ষণ না আপনি বন্ধ করছেন"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index 17c0d1daa0fb..2c05aa5bc9b4 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -435,13 +435,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"izmjene postavki zvuka"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Omogućava aplikaciji izmjenu općih postavki zvuka, kao što su jačina zvuka i izbor izlaznog zvučnika."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"snimanje audiozapisa"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ova aplikacija može u svakom trenutku snimati zvuk koristeći mikrofon."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"slanje komandi SIM kartici"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućava aplikaciji slanje naredbi na SIM. Ovo je vrlo opasno."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje fizičke aktivnosti"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ova aplikacija može prepoznati vašu fizičku aktivnost."</string> <string name="permlab_camera" msgid="6320282492904119413">"snimanje slika i videozapisa"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Ova aplikacija može slikati fotografije i snimati videozapise koristeći kameru bilo kada."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Dopustite aplikaciji ili usluzi da pristupa kamerama sistema radi snimanja fotografija i videozapisa"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ova povlaštena ili sistemska aplikacija u svakom trenutku može snimati fotografije i videozapise pomoću kamere sistema. Aplikacija također mora imati odobrenje android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Dozvoliti aplikaciji ili usluzi da prima povratne pozive o otvaranju ili zatvaranju kamera."</string> @@ -1644,10 +1654,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite li pojačati zvuk iznad preporučenog nivoa?\n\nDužim slušanjem glasnog zvuka možete oštetiti sluh."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li koristiti Prečicu za pristupačnost?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kada je prečica uključena, pritiskom i držanjem oba dugmeta za jačinu zvuka u trajanju od 3 sekunde pokrenut će se funkcija pristupačnosti."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Uključiti funkcije pristupačnosti?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Uključiti prečicu za funkcije pristupačnosti?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ako nekoliko sekundi držite pritisnute obje tipke za jačinu zvuka, uključit ćete funkcije pristupačnosti. Ovo može uticati na način rada uređaja.\n\nTrenutne funkcije:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nOdabrane funkcije možete promijeniti u odjeljku Postavke > Pristupačnost."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Uključiti <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Uključiti prečicu za uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ako nekoliko sekundi držite pritisnute obje tipke za jačinu zvuka, uključit ćete funkciju pristupačnosti <xliff:g id="SERVICE">%1$s</xliff:g>. Ovo može promijeniti način rada uređaja.\n\nOvu prečicu možete zamijeniti drugom funkcijom u odjeljku Postavke > Pristupačnost."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Uključi"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nemoj uključiti"</string> @@ -1860,7 +1870,7 @@ <item quantity="few">%d sata</item> <item quantity="other">%d sati</item> </plurals> - <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> + <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Do: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_until" msgid="2250286190237669079">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="7046911727540499275">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (sljedeći alarm)"</string> <string name="zen_mode_forever" msgid="740585666364912448">"Dok ne isključite"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index e0c709d4e380..0a25ba02b602 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"canviar la configuració d\'àudio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permet que l\'aplicació modifiqui la configuració d\'àudio general, com ara el volum i l\'altaveu de sortida que es fa servir."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"gravar àudio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Aquesta aplicació pot gravar àudio amb el micròfon en qualsevol moment."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"enviar ordres a la SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permet que l\'aplicació enviï ordres a la SIM. Això és molt perillós."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconèixer l\'activitat física"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Aquesta aplicació pot reconèixer la teva activitat física."</string> <string name="permlab_camera" msgid="6320282492904119413">"fer fotos i vídeos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Aquesta aplicació pot fer fotos i gravar vídeos amb la càmera en qualsevol moment."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Permet que una aplicació o un servei tinguin accés a les càmeres del sistema per fer fotos i vídeos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Aquesta aplicació del sistema amb privilegis pot fer fotos i gravar vídeos amb una càmera del sistema en qualsevol moment. L\'aplicació també ha de tenir el permís android.permission.CAMERA per accedir-hi."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permet que una aplicació o un servei pugui rebre crides de retorn sobre els dispositius de càmera que s\'obren o es tanquen."</string> @@ -991,8 +1001,8 @@ <string name="searchview_description_submit" msgid="6771060386117334686">"Envia la consulta"</string> <string name="searchview_description_voice" msgid="42360159504884679">"Cerca per veu"</string> <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"Vols activar l\'exploració tàctil?"</string> - <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> vol activar l\'exploració tàctil. Quan l\'exploració tàctil està activada, pots escoltar o veure les descripcions del contingut seleccionat o utilitzar gestos per interactuar amb la tauleta."</string> - <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> vol activar l\'exploració tàctil. Quan l\'exploració per tàctil està activada, pots escoltar o veure les descripcions del contingut seleccionat o utilitzar gestos per interactuar amb el telèfon."</string> + <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> vol activar l\'exploració tàctil. Quan l\'exploració tàctil està activada, pots escoltar o veure les descripcions del contingut seleccionat o utilitzar gestos per interaccionar amb la tauleta."</string> + <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> vol activar l\'exploració tàctil. Quan l\'exploració per tàctil està activada, pots escoltar o veure les descripcions del contingut seleccionat o utilitzar gestos per interaccionar amb el telèfon."</string> <string name="oneMonthDurationPast" msgid="4538030857114635777">"Fa 1 mes"</string> <string name="beforeOneMonthDurationPast" msgid="8315149541372065392">"Fa més d\'1 mes"</string> <plurals name="last_num_days" formatted="false" msgid="687443109145393632"> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vols apujar el volum per sobre del nivell recomanat?\n\nSi escoltes música a un volum alt durant períodes llargs, pots danyar-te l\'oïda."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vols fer servir la drecera d\'accessibilitat?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Si la drecera està activada, prem els dos botons de volum durant 3 segons per iniciar una funció d\'accessibilitat."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Vols activar les funcions d\'accessibilitat?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vols desactivar la drecera de les funcions d\'accessibilitat?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Si mantens premudes les dues tecles de volum durant uns segons, s\'activaran les funcions d\'accessibilitat. Això podria canviar el funcionament del teu dispositiu.\n\nFuncions actuals:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPots canviar les funcions seleccionades a Configuració > Accessibilitat."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Vols activar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vols activar la drecera de <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Si mantens premudes les dues tecles de volum durant uns segons, la funció d\'accessibilitat <xliff:g id="SERVICE">%1$s</xliff:g> s\'activarà. Això podria canviar el funcionament del teu dispositiu.\n\nPots canviar la funció d\'aquesta drecera a Configuració > Accessibilitat."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activa"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"No activis"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 095ec69af783..8321f2295026 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -438,13 +438,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"změna nastavení zvuku"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Umožňuje aplikaci změnit globální nastavení zvuku, například hlasitost či reproduktor pro výstup zvuku."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"nahrávání zvuku"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Tato aplikace může pomocí mikrofonu kdykoli zaznamenat zvuk."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"odesílání příkazů do SIM karty"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Umožňuje aplikaci odesílat příkazy na kartu SIM. Toto oprávnění je velmi nebezpečné."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"rozpoznávání fyzické aktivity"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Tyto aplikace dokážou rozpoznat vaši fyzickou aktivitu."</string> <string name="permlab_camera" msgid="6320282492904119413">"pořizování fotografií a videí"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Tato aplikace může pomocí fotoaparátu kdykoli pořídit snímek nebo nahrát video."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Povolte aplikaci nebo službě k systémovým fotoaparátům za účelem pořizování fotek a videí"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Tato privilegovaná nebo systémová aplikace může pomocí fotoaparátu kdykoli pořídit snímek nebo nahrát video. Aplikace musí zároveň mít oprávnění android.permission.CAMERA."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Povolte aplikaci nebo službě přijímat zpětná volání o otevření nebo zavření zařízení s fotoaparátem."</string> @@ -1666,10 +1676,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Zvýšit hlasitost nad doporučenou úroveň?\n\nDlouhodobý poslech hlasitého zvuku může poškodit sluch."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Použít zkratku přístupnosti?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Když je tato zkratka zapnutá, můžete funkci přístupnosti spustit tím, že na tři sekundy podržíte obě tlačítka hlasitosti."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Zapnout funkce pro usnadnění přístupu?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Zapnout zkratku funkcí pro usnadnění přístupu?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Podržením obou tlačítek hlasitosti po dobu několika sekund zapnete funkce pro usnadnění přístupu. Tato funkce může změnit fungování zařízení.\n\nAktuální funkce:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nVybrané funkce můžete změnit v Nastavení > Přístupnost."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Zapnout <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Zapnout zkratku služby <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Podržením obou tlačítek hlasitosti po dobu několika sekund zapnete funkci pro usnadnění přístupu <xliff:g id="SERVICE">%1$s</xliff:g>. Tato funkce může změnit fungování zařízení.\n\nZkratku můžete nastavit na jinou funkci v Nastavení > Přístupnost."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Zapnout"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nezapínat"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 499b9dee0d87..d85db8fa82ca 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"skifte dine lydindstillinger"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Tillader, at appen kan ændre globale lydindstillinger, som f.eks. lydstyrke og hvilken højttaler der bruges til output."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"optage lyd"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Denne app kan til enhver tid optage lyd via mikrofonen."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"send kommandoer til SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Tillader, at appen sender kommandoer til SIM-kortet. Dette er meget farligt."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"genkend fysisk aktivitet"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Denne app kan genkende din fysiske aktivitet."</string> <string name="permlab_camera" msgid="6320282492904119413">"tage billeder og optage video"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Med denne app kan du tage billeder og optage video med kameraet når som helst."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Giv en app eller tjeneste adgang til systemkameraer for at tage billeder og optage video"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Denne privilegerede app eller systemapp kan til enhver tid tage billeder og optage video med et systemkamera. Appen skal også have tilladelsen android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Tillad, at en app eller tjeneste modtager tilbagekald om kameraenheder, der åbnes eller lukkes."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vil du skrue højere op end det anbefalede lydstyrkeniveau?\n\nDu kan skade hørelsen ved at lytte til meget høj musik over længere tid."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vil du bruge genvejen til Hjælpefunktioner?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Når genvejen er aktiveret, kan du starte en hjælpefunktion ved at trykke på begge lydstyrkeknapper i tre sekunder."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Vil du aktivere hjælpefunktionerne?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vil du aktivere genvejen til hjælpefunktioner?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hvis du holder begge lydstyrkeknapperne nede i et par sekunder, aktiveres hjælpefunktionerne. Det kan ændre på, hvordan din enhed fungerer.\n\nAktuelle funktioner:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kan ændre de valgte funktioner i Indstillinger > Hjælpefunktioner."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Vil du aktivere <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vil du aktivere <xliff:g id="SERVICE">%1$s</xliff:g>-genvejen?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Hvis du holder begge lydstyrkeknapperne nede i et par sekunder, aktiveres hjælpefunktionen <xliff:g id="SERVICE">%1$s</xliff:g>. Det kan ændre på, hvordan din enhed fungerer.\n\nDu kan ændre denne genvej til en anden funktion i Indstillinger > Hjælpefunktioner."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivér"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Aktivér ikke"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 867efac7df21..e4fabbcc548d 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"Audio-Einstellungen ändern"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Ermöglicht der App, globale Audio-Einstellungen zu ändern, etwa die Lautstärke und den Lautsprecher für die Ausgabe."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"Audio aufnehmen"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Diese App kann jederzeit Audio über das Mikrofon aufnehmen."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"Befehle an die SIM senden"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Ermöglicht der App das Senden von Befehlen an die SIM-Karte. Dies ist äußerst risikoreich."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"Körperliche Aktivitäten erkennen"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Diese App kann deine körperliche Aktivität erkennen."</string> <string name="permlab_camera" msgid="6320282492904119413">"Bilder und Videos aufnehmen"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Diese App kann mit der Kamera jederzeit Bilder und Videos aufnehmen."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Einer App oder einem Dienst Zugriff auf Systemkameras erlauben, um Fotos und Videos aufnehmen zu können"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Diese privilegierte App oder System-App kann jederzeit mit einer Systemkamera Bilder und Videos aufnehmen. Die App benötigt auch die Berechtigung \"android.permission.CAMERA\"."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Einer App oder einem Dienst den Empfang von Callbacks erlauben, wenn eine Kamera geöffnet oder geschlossen wird."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Lautstärke über den Schwellenwert anheben?\n\nWenn du über einen längeren Zeitraum Musik in hoher Lautstärke hörst, kann dies dein Gehör schädigen."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Verknüpfung für Bedienungshilfen verwenden?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Wenn die Verknüpfung aktiviert ist, kannst du die beiden Lautstärketasten drei Sekunden lang gedrückt halten, um eine Bedienungshilfe zu starten."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Bedienungshilfen aktivieren?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Verknüpfung für Bedienungshilfen aktivieren?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Wenn du beide Lautstärketasten einige Sekunden lang gedrückt hältst, aktivierst du die Bedienungshilfen. Dadurch kann sich die Funktionsweise deines Geräts ändern.\n\nAktuelle Funktionen:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kannst ausgewählte Funktionen unter \"Einstellungen\" > \"Bedienungshilfen\" ändern."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> aktivieren?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Verknüpfung für <xliff:g id="SERVICE">%1$s</xliff:g> aktivieren?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Wenn du beide Lautstärketasten einige Sekunden lang gedrückt hältst, aktivierst du die Bedienungshilfe \"<xliff:g id="SERVICE">%1$s</xliff:g>\". Dadurch kann sich die Funktionsweise deines Geräts ändern.\n\nUnter \"Einstellungen > \"Bedienungshilfen\" kannst du dieser Verknüpfung eine andere Funktion zuweisen."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivieren"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nicht aktivieren"</string> @@ -1792,8 +1802,8 @@ <string name="package_updated_device_owner" msgid="7560272363805506941">"Von deinem Administrator aktualisiert"</string> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Von deinem Administrator gelöscht"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> - <string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Der Energiesparmodus sorgt für eine längere Akkulaufzeit:\n\n• Das dunkle Design wird aktiviert\n• Hintergrundaktivitäten, einige optische Effekte und weitere Funktionen wie \"Ok Google\" werden abgeschaltet oder eingeschränkt\n\n"<annotation id="url">"Weitere Informationen"</annotation></string> - <string name="battery_saver_description" msgid="6794188153647295212">"Der Energiesparmodus sorgt für eine längere Akkulaufzeit:\n\n• Das dunkle Design wird aktiviert\n• Hintergrundaktivitäten, einige optische Effekte und weitere Funktionen wie \"Ok Google\" werden abgeschaltet oder eingeschränkt"</string> + <string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Der Stromsparmodus sorgt für eine längere Akkulaufzeit:\n\n• Das dunkle Design wird aktiviert\n• Hintergrundaktivitäten, einige optische Effekte und weitere Funktionen wie \"Ok Google\" werden abgeschaltet oder eingeschränkt\n\n"<annotation id="url">"Weitere Informationen"</annotation></string> + <string name="battery_saver_description" msgid="6794188153647295212">"Der Stromsparmodus sorgt für eine längere Akkulaufzeit:\n\n• Das dunkle Design wird aktiviert\n• Hintergrundaktivitäten, einige optische Effekte und weitere Funktionen wie \"Ok Google\" werden abgeschaltet oder eingeschränkt"</string> <string name="data_saver_description" msgid="4995164271550590517">"Der Datensparmodus verhindert zum einen, dass manche Apps im Hintergrund Daten senden oder empfangen, sodass weniger Daten verbraucht werden. Zum anderen werden die Datenzugriffe der gerade aktiven App eingeschränkt, was z. B. dazu führen kann, dass Bilder erst angetippt werden müssen, bevor sie sichtbar werden."</string> <string name="data_saver_enable_title" msgid="7080620065745260137">"Datensparmodus aktivieren?"</string> <string name="data_saver_enable_button" msgid="4399405762586419726">"Aktivieren"</string> @@ -1829,8 +1839,7 @@ <item quantity="other">Für %d h</item> <item quantity="one">Für 1 h</item> </plurals> - <!-- no translation found for zen_mode_until_next_day (1403042784161725038) --> - <skip /> + <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Bis <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_until" msgid="2250286190237669079">"Bis <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="7046911727540499275">"Bis <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (nächste Weckzeit)"</string> <string name="zen_mode_forever" msgid="740585666364912448">"Bis zur Deaktivierung"</string> @@ -2002,9 +2011,9 @@ <string name="notification_feedback_indicator" msgid="663476517711323016">"Feedback geben"</string> <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Infomitteilung zum Ablaufmodus"</string> <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Dein Akku könnte vor der gewöhnlichen Ladezeit leer sein"</string> - <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Energiesparmodus aktiviert, um die Akkulaufzeit zu verlängern"</string> - <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Energiesparmodus"</string> - <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Energiesparmodus deaktiviert"</string> + <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Stromsparmodus aktiviert, um die Akkulaufzeit zu verlängern"</string> + <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Stromsparmodus"</string> + <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Stromsparmodus deaktiviert"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Das Smartphone ist ausreichend geladen. Es sind keine Funktionen mehr beschränkt."</string> <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Das Tablet ist ausreichend geladen. Es sind keine Funktionen mehr beschränkt."</string> <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Das Gerät ist ausreichend geladen. Es sind keine Funktionen mehr beschränkt."</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index d4d1c5a43374..d9badc6cf257 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"αλλάζει τις ρυθμίσεις ήχου"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Επιτρέπει στην εφαρμογή την τροποποίηση καθολικών ρυθμίσεων ήχου, όπως η ένταση και ποιο ηχείο χρησιμοποιείται για έξοδο."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"εγγράφει ήχο"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Αυτή η εφαρμογή μπορεί να κάνει εγγραφή ήχου χρησιμοποιώντας το μικρόφωνο, ανά πάσα στιγμή."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"στέλνει εντολές στην κάρτα SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Επιτρέπει στην εφαρμογή την αποστολή εντολών στην κάρτα SIM. Αυτό είναι εξαιρετικά επικίνδυνο."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"αναγνώριση σωματικής δραστηριότητας"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Αυτή η εφαρμογή μπορεί να αναγνωρίσει τη σωματική σας δραστηριότητα."</string> <string name="permlab_camera" msgid="6320282492904119413">"κάνει λήψη φωτογραφιών και βίντεο"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Αυτή η εφαρμογή μπορεί να τραβήξει φωτογραφίες και βίντεο χρησιμοποιώντας την κάμερα, ανά πάσα στιγμή."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Παραχωρήστε σε μια εφαρμογή ή υπηρεσία πρόσβαση στις κάμερες του συστήματος για τη λήψη φωτογραφιών και βίντεο"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Αυτή η προνομιακή εφαρμογή ή εφαρμογή συστήματος μπορεί να τραβάει φωτογραφίες και να εγγράφει βίντεο, χρησιμοποιώντας μια κάμερα του συστήματος ανά πάσα στιγμή. Απαιτείται, επίσης, η εφαρμογή να έχει την άδεια android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Επιτρέψτε σε μια εφαρμογή ή μια υπηρεσία να λαμβάνει επανάκλησεις σχετικά με το άνοιγμα ή το κλείσιμο συσκευών κάμερας."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Αυξάνετε την ένταση ήχου πάνω από το επίπεδο ασφαλείας;\n\nΑν ακούτε μουσική σε υψηλή ένταση για μεγάλο χρονικό διάστημα ενδέχεται να προκληθεί βλάβη στην ακοή σας."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Να χρησιμοποιείται η συντόμευση προσβασιμότητας;"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Όταν η συντόμευση είναι ενεργοποιημένη, το πάτημα και των δύο κουμπιών έντασης ήχου για 3 δευτερόλεπτα θα ξεκινήσει μια λειτουργία προσβασιμότητας."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Θέλετε να ενεργοποιήσετε τις λειτουργίες προσβασιμότητας;"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Ενεργοποίηση συντόμευσης για λειτουργίες προσβασιμότητας;"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Για να ενεργοποιήσετε τις λειτουργίες προσβασιμότητας, πατήστε παρατεταμένα τα δύο πλήκτρα έντασης για μερικά δευτερόλεπτα. Αυτό ενδέχεται να αλλάξει τον τρόπο λειτουργίας της συσκευής σας.\n\nΤρέχουσες λειτουργίες:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nΜπορείτε να αλλάξετε τις επιλεγμένες λειτουργίες στις Ρυθμίσεις > Προσβασιμότητα."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Θέλετε να ενεργοποιήσετε τη λειτουργία <xliff:g id="SERVICE">%1$s</xliff:g>;"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Ενεργοποίηση συντόμευσης <xliff:g id="SERVICE">%1$s</xliff:g>;"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Μπορείτε να ενεργοποιήσετε τη λειτουργία <xliff:g id="SERVICE">%1$s</xliff:g>, η οποία είναι μία από τις λειτουργίες προσβασιμότητας, πατώντας παρατεταμένα ταυτόχρονα τα δύο πλήκτρα έντασης ήχου για μερικά δευτερόλεπτα. Αυτό ενδέχεται να αλλάξει τον τρόπο λειτουργίας της συσκευής σας.\n\nΜπορείτε να αλλάξετε αυτή τη συντόμευση σε μια άλλη λειτουργία στις Ρυθμίσεις > Προσβασιμότητα."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ενεργοποίηση"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Να μην ενεργοποιηθούν"</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index f140ce3fe155..60f7a0993b92 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"This app can record audio using the microphone at any time."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognise your physical activity."</string> <string name="permlab_camera" msgid="6320282492904119413">"take pictures and videos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"This app can take pictures and record videos using the camera at any time."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Grant an application or service access to system cameras to take pictures and videos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Allow an application or service to receive callbacks about camera devices being opened or closed."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Turn on accessibility features?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings > Accessibility."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings > Accessibility."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Don’t turn on"</string> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index e814bdfbad83..ae3aaab89de2 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"This app can record audio using the microphone at any time."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognise your physical activity."</string> <string name="permlab_camera" msgid="6320282492904119413">"take pictures and videos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"This app can take pictures and record videos using the camera at any time."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Grant an application or service access to system cameras to take pictures and videos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Allow an application or service to receive callbacks about camera devices being opened or closed."</string> @@ -1123,8 +1133,8 @@ <string name="loading" msgid="3138021523725055037">"Loading…"</string> <string name="capital_on" msgid="2770685323900821829">"ON"</string> <string name="capital_off" msgid="7443704171014626777">"OFF"</string> - <string name="checked" msgid="9179896827054513119">"ticked"</string> - <string name="not_checked" msgid="7972320087569023342">"not ticked"</string> + <string name="checked" msgid="9179896827054513119">"checked"</string> + <string name="not_checked" msgid="7972320087569023342">"not checked"</string> <string name="whichApplication" msgid="5432266899591255759">"Complete action using"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"Complete action using %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Complete action"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Turn on accessibility features?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings > Accessibility."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings > Accessibility."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Don’t turn on"</string> @@ -2025,7 +2035,7 @@ <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> spreadsheet"</string> <string name="mime_type_presentation" msgid="1145384236788242075">"Presentation"</string> <string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g> presentation"</string> - <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"Bluetooth will stay on during aeroplane mode"</string> + <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"Bluetooth will stay on in Airplane mode"</string> <string name="car_loading_profile" msgid="8219978381196748070">"Loading"</string> <plurals name="file_count" formatted="false" msgid="7063513834724389247"> <item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> files</item> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 73f1562e5058..52dcf979a45f 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"This app can record audio using the microphone at any time."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognise your physical activity."</string> <string name="permlab_camera" msgid="6320282492904119413">"take pictures and videos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"This app can take pictures and record videos using the camera at any time."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Grant an application or service access to system cameras to take pictures and videos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Allow an application or service to receive callbacks about camera devices being opened or closed."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Turn on accessibility features?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings > Accessibility."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings > Accessibility."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Don’t turn on"</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 122ea7fa7386..2f7f271678e9 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"This app can record audio using the microphone at any time."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognise your physical activity."</string> <string name="permlab_camera" msgid="6320282492904119413">"take pictures and videos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"This app can take pictures and record videos using the camera at any time."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Grant an application or service access to system cameras to take pictures and videos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Allow an application or service to receive callbacks about camera devices being opened or closed."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Turn on accessibility features?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings > Accessibility."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings > Accessibility."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Don’t turn on"</string> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 53d8c6653461..8f017f911b73 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"This app can record audio using the microphone at any time."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognize physical activity"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognize your physical activity."</string> <string name="permlab_camera" msgid="6320282492904119413">"take pictures and videos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"This app can take pictures and record videos using the camera at any time."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Allow an application or service access to system cameras to take pictures and videos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Allow an application or service to receive callbacks about camera devices being opened or closed."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Turn on accessibility features?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings > Accessibility."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings > Accessibility."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Don’t turn on"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 953b6fc3a9d7..f933a2fae10f 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"cambiar tu configuración de audio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que la aplicación modifique la configuración de audio global, por ejemplo, el volumen y el altavoz de salida."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"grabar audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Esta app puede grabar audio con el micrófono en cualquier momento."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos a la tarjeta SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que la aplicación envíe comandos a la tarjeta SIM. Usar este permiso es peligroso."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconocer actividad física"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Esta app puede reconocer tu actividad física."</string> <string name="permlab_camera" msgid="6320282492904119413">"tomar fotografías y grabar videos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Esta app puede tomar fotos y grabar videos con la cámara en cualquier momento."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Permitir que una aplicación o un servicio accedan a las cámaras del sistema para tomar fotos y grabar videos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Esta app del sistema o con privilegios puede tomar fotografías y grabar videos con una cámara del sistema en cualquier momento. Para ello, requiere tener el permiso android.permission.CAMERA."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permite que una aplicación o un servicio reciba devoluciones de llamada cuando se abren o cierran dispositivos de cámara."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"¿Quieres subir el volumen por encima del nivel recomendado?\n\nEscuchar a un alto volumen durante largos períodos puede dañar tu audición."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"¿Usar acceso directo de accesibilidad?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Cuando la combinación de teclas está activada, puedes presionar los botones de volumen durante 3 segundos para iniciar una función de accesibilidad."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"¿Quieres activar las funciones de accesibilidad?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"¿Quieres activar la combinación de teclas para las funciones de accesibilidad?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Si mantienes presionadas las dos teclas de volumen durante unos segundos, se activarán las funciones de accesibilidad. Esto puede cambiar el funcionamiento de tu dispositivo.\n\nFunciones actuales:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuedes cambiar las funciones seleccionadas en Configuración > Accesibilidad."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"¿Quieres activar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"¿Quieres activar el acceso directo de <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Si mantienes presionadas ambas teclas de volumen durante unos segundos, se activará la función de accesibilidad <xliff:g id="SERVICE">%1$s</xliff:g>. Esto podría cambiar la forma en que funciona tu dispositivo.\n\nPuedes cambiar este acceso directo a otra función en Configuración > Accesibilidad."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activar"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"No activar"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 465d2ec63d3a..834c901eea46 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"cambiar la configuración de audio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que la aplicación modifique la configuración de audio global (por ejemplo, el volumen y el altavoz de salida)."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"grabar sonido"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Esta aplicación puede grabar audio con el micrófono en cualquier momento."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos a la tarjeta SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que la aplicación envíe comandos a la tarjeta SIM. Este permiso es muy peligroso."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconocer actividad física"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Esta aplicación puede reconocer tu actividad física."</string> <string name="permlab_camera" msgid="6320282492904119413">"realizar fotografías y vídeos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Esta aplicación puede hacer fotografías y grabar vídeos con la cámara en cualquier momento."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Permitir que una aplicación o servicio acceda a las cámaras del sistema para hacer fotos y vídeos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Esta aplicación del sistema o con privilegios puede hacer fotos y grabar vídeos en cualquier momento con una cámara del sistema, aunque debe tener también el permiso android.permission.CAMERA para hacerlo"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permitir que una aplicación o servicio reciba retrollamadas cada vez que se abra o cierre una cámara."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"¿Quieres subir el volumen por encima del nivel recomendado?\n\nEscuchar sonidos fuertes durante mucho tiempo puede dañar los oídos."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"¿Utilizar acceso directo de accesibilidad?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Si el acceso directo está activado, pulsa los dos botones de volumen durante 3 segundos para iniciar una función de accesibilidad."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"¿Activar funciones de accesibilidad?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"¿Quieres activar el acceso directo a las funciones de accesibilidad?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Al mantener pulsadas las dos teclas de volumen durante unos segundos, se activan las funciones de accesibilidad, que pueden cambiar el funcionamiento del dispositivo.\n\nFunciones actuales:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuedes cambiar las funciones seleccionadas en Ajustes > Accesibilidad."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"¿Quieres activar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"¿Quieres activar el acceso directo a <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Al mantener pulsadas ambas teclas de volumen durante unos segundos se activa <xliff:g id="SERVICE">%1$s</xliff:g>, una función de accesibilidad. Esta función puede modificar el funcionamiento del dispositivo.\n\nPuedes asignar este acceso directo a otra función en Ajustes > Accesibilidad."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activar"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"No activar"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 6d2a4c0f2bfb..babd58d310de 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"muuda heliseadeid"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Võimaldab rakendusel muuta üldiseid heliseadeid, näiteks helitugevust ja seda, millist kõlarit kasutatakse väljundiks."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"salvesta heli"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"See rakendus saab mikrofoni kasutades mis tahes ajal heli salvestada."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM-kaardile käskluste saatmine"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Lubab rakendusel saata käske SIM-kaardile. See on väga ohtlik."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"füüsiliste tegevuste tuvastamine"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"See rakendus saab tuvastada teie füüsilised tegevused."</string> <string name="permlab_camera" msgid="6320282492904119413">"piltide ja videote tegemine"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"See rakendus saab mis tahes ajal kaameraga pildistada ja videoid salvestada."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Rakendusel või teenusel lubatakse süsteemi kaameratele juurde pääseda, et pilte ja videoid jäädvustada"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"See privileegidega või süsteemirakendus saab süsteemi kaameraga alati pilte ja videoid jäädvustada. Rakendusel peab olema ka luba android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Lubab rakendusel või teenusel kaameraseadmete avamise või sulgemise kohta tagasikutseid vastu võtta."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Kas suurendada helitugevuse taset üle soovitatud taseme?\n\nPikaajaline valju helitugevusega kuulamine võib kuulmist kahjustada."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Kas kasutada juurdepääsetavuse otseteed?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kui otsetee on sisse lülitatud, käivitab mõlema helitugevuse nupu kolm sekundit all hoidmine juurdepääsetavuse funktsiooni."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Kas lülitada juurdepääsufunktsioonid sisse?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Kas lülitada juurdepääsufunktsioonide otsetee sisse?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hoidke juurdepääsufunktsioonide sisselülitamiseks mõlemat helitugevuse klahvi mõni sekund all. See võib teie seadme tööviisi muuta.\n\nPraegused funktsioonid:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nValitud funktsioone saab muuta jaotises Seaded > Juurdepääsetavus."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Kas lülitada <xliff:g id="SERVICE">%1$s</xliff:g> sisse?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Kas lülitada teenuse <xliff:g id="SERVICE">%1$s</xliff:g> otsetee sisse?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Kui hoiate mõlemat helitugevuse klahvi mõni sekund all, lülitatakse juurdepääsufunktsioon <xliff:g id="SERVICE">%1$s</xliff:g> sisse. See võib teie seadme tööviisi muuta.\n\nSelle otsetee saab asendada muu otseteega jaotises Seaded > Juurdepääsetavus."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Lülita sisse"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ära lülita sisse"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 7241b974c3c8..3ba099b3b411 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"aldatu audio-ezarpenak"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Audio-ezarpen orokorrak aldatzeko baimena ematen dio; besteak beste, bolumena eta irteerarako zer bozgorailu erabiltzen den."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"grabatu audioa"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Aplikazioak edonoiz erabil dezake mikrofonoa audioa grabatzeko."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"bidali aginduak SIM txartelera"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM txartelera aginduak bidaltzeko aukera ematen die aplikazioei. Oso arriskutsua da."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"hauteman ariketa fisikoa"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Aplikazioak ariketa fisikoa hauteman dezake."</string> <string name="permlab_camera" msgid="6320282492904119413">"atera argazkiak eta grabatu bideoak"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Aplikazioak edonoiz erabil dezake kamera argazkiak ateratzeko eta bideoak grabatzeko."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"onartu aplikazio edo zerbitzu bati sistemako kamerak atzitzea argazkiak eta bideoak ateratzeko"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Pribilegioa duen edo sistemakoa den aplikazio honek edonoiz erabil dezake kamera argazkiak ateratzeko eta bideoak grabatzeko. Halaber, android.permission.CAMERA baimena izan behar du aplikazioak."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"eman jakinarazpenak jasotzeko baimena aplikazioari edo zerbitzuari kamerak ireki edo ixten direnean."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Bolumena gomendatutako mailatik gora igo nahi duzu?\n\nMusika bolumen handian eta denbora luzez entzuteak entzumena kalte diezazuke."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Erabilerraztasun-lasterbidea erabili nahi duzu?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Lasterbidea aktibatuta dagoenean, bi bolumen-botoiak hiru segundoz sakatuta abiaraziko da erabilerraztasun-eginbidea."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Erabilerraztasun-eginbideak aktibatu nahi dituzu?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Erabilerraztasun-eginbideetarako lasterbidea aktibatu nahi duzu?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Eduki sakatuta bolumen-botoiak segundo batzuez erabilerraztasun-eginbideak aktibatzeko. Hori eginez gero, baliteke zure mugikorraren funtzionamendua aldatzea.\n\nUneko eginbideak:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nHautatutako eginbideak aldatzeko, joan Ezarpenak > Erabilerraztasuna atalera."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> aktibatu nahi duzu?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> zerbitzuaren lasterbidea aktibatu nahi duzu?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Eduki sakatuta bolumen-botoiak segundo batzuez <xliff:g id="SERVICE">%1$s</xliff:g> izeneko erabilerraztasun-eginbidea aktibatzeko. Honen bidez, baliteke zure mugikorraren funtzionamendua aldatzea.\n\nLasterbide hau beste eginbide batengatik aldatzeko, joan Ezarpenak > Erabilerraztasuna atalera."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktibatu"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ez aktibatu"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 02f4728de45e..7750d5d50342 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"تغییر تنظیمات صوتی"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"به برنامه امکان میدهد تنظیمات صوتی کلی مانند میزان صدا و بلندگوی مورد استفاده برای پخش صدا را تغییر دهد."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ضبط صدا"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"این برنامه میتواند در هرزمانی با استفاده از میکروفون صدا ضبط کند."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"ارسال فرمان به سیم کارت"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"به برنامه اجازه ارسال دستورات به سیم کارت را میدهد. این بسیار خطرناک است."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"تشخیص فعالیت فیزیکی"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"این برنامه نمیتواند فعالیت فیزیکیتان را تشخیص دهد."</string> <string name="permlab_camera" msgid="6320282492904119413">"عکسبرداری و فیلمبرداری"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"این برنامه میتواند در هرزمانی با استفاده از دوربین عکس و فیلم بگیرد."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"به برنامه یا سرویسی اجازه دهید برای عکسبرداری و فیلمبرداری به دوربینهای سیستم دسترسی داشته باشد"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"این برنامه سیستم یا دارای امتیاز، میتواند با استفاده از دوربین سیستم در هرزمانی عکسبرداری و فیلمبرداری کند. برنامه به اجازه android.permission.CAMERA هم نیاز دارد."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"مجاز کردن برنامه یا سرویس برای دریافت پاسخ تماس درباره دستگاههای دوربینی که باز یا بسته میشوند."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"میزان صدا را به بالاتر از حد توصیه شده افزایش میدهید؟\n\nگوش دادن به صداهای بلند برای مدت طولانی میتواند به شنواییتان آسیب وارد کند."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"از میانبر دسترسپذیری استفاده شود؟"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"وقتی میانبر روشن باشد، با فشار دادن هردو دکمه صدا بهمدت ۳ ثانیه ویژگی دسترسپذیری فعال میشود."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ویژگیهای دسترسپذیری روشن شود؟"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"میانبر برای ویژگیهای دسترسپذیری روشن شود؟"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"با پایین نگه داشتن هردو کلید میزان صدا بهمدت چند ثانیه، ویژگیهای دسترسپذیری روشن میشود. با این کار نحوه عملکرد دستگاهتان تغییر میکند.\n\nویژگیهای فعلی:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nمیتوانید ویژگیهای انتخابی را در «تنظیمات > دسترسپذیری» تغییر دهید."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> روشن شود؟"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"میانبر <xliff:g id="SERVICE">%1$s</xliff:g> روشن شود؟"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"با پایین نگه داشتن هردو کلید میزان صدا بهمدت چند ثانیه، <xliff:g id="SERVICE">%1$s</xliff:g> (یکی از ویژگیهای دسترسپذیری) روشن میشود. با این کار نحوه عملکرد دستگاهتان تغییر میکند.\n\nمیتوانید در «تنظیمات > دسترسپذیری»،این میانبر را به ویژگی دیگری تغییر دهید."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"روشن شود"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"روشن نشود"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 0c94f35de91b..616223b38e0f 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"muuta ääniasetuksia"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Antaa sovelluksen muokata yleisiä ääniasetuksia, kuten äänenvoimakkuutta ja käytettävää kaiutinta."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"tallentaa ääntä"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Tämä sovellus voi tallentaa mikrofonilla ääntä koska tahansa."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"lähettää komentoja SIM-kortille"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Antaa sovelluksen lähettää komentoja SIM-kortille. Tämä ei ole turvallista."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"tunnistaa liikkumisen"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Sovellus voi tunnistaa liikkumisesi."</string> <string name="permlab_camera" msgid="6320282492904119413">"ota kuvia ja videoita"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Tämä sovellus voi ottaa kameralla kuvia ja videoita koska tahansa."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Salli sovellukselle tai palvelulle pääsy järjestelmän kameroihin, jotta se voi ottaa kuvia ja nauhoittaa videoita"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Tämä oikeutettu tai järjestelmäsovellus voi ottaa järjestelmän kameralla kuvia ja videoita koska tahansa. Sovelluksella on oltava myös android.permission.CAMERA-lupa"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Salli sovelluksen tai palvelun vastaanottaa vastakutsuja kameralaitteiden avaamisesta tai sulkemisesta."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Nostetaanko äänenvoimakkuus suositellun tason yläpuolelle?\n\nPitkäkestoinen kova äänenvoimakkuus saattaa heikentää kuuloa."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Käytetäänkö esteettömyyden pikanäppäintä?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kun pikanäppäin on käytössä, voit käynnistää esteettömyystoiminnon pitämällä molempia äänenvoimakkuuspainikkeita painettuna kolmen sekunnin ajan."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Laitetaanko esteettömyysominaisuudet päälle?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Laitetaanko esteettömyysominaisuuksien pikavalinta päälle?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Molempien äänenvoimakkuuspainikkeiden painaminen muutaman sekunnin ajan laittaa esteettömyysominaisuudet päälle. Tämä voi muuttaa laitteesi toimintaa.\n\nNykyiset ominaisuudet:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nVoit muuttaa valittuja ominaisuuksia kohdassa Asetukset > Esteettömyys."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Laitetaanko <xliff:g id="SERVICE">%1$s</xliff:g> päälle?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Laitetaanko pikavalinta (<xliff:g id="SERVICE">%1$s</xliff:g>) päälle?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Molempien äänenvoimakkuuspainikkeiden pitkään painaminen laittaa päälle esteettömyysominaisuuden <xliff:g id="SERVICE">%1$s</xliff:g>. Tämä voi muuttaa laitteesi toimintaa.\n\nVoit muuttaa tätä pikanäppäintä kohdassa Asetukset > Esteettömyys."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Laita päälle"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Älä laita päälle"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index dcfe8ad100cb..61112702f463 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modifier vos paramètres audio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permet à l\'application de modifier les paramètres audio généraux, tels que le volume et la sortie audio utilisée."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"enregistrer des fichiers audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Cette application peut enregistrer de l\'audio à l\'aide du microphone en tout temps."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"envoyer des commandes à la carte SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permet à l\'application d\'envoyer des commandes à la carte SIM. Cette fonctionnalité est très dangereuse."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconnaître les activités physiques"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Cette application peut reconnaître vos activités physiques."</string> <string name="permlab_camera" msgid="6320282492904119413">"prendre des photos et filmer des vidéos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Cette application peut prendre des photos et enregistrer des vidéos à l\'aide de l\'appareil photo en tout temps."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Autoriser une application ou un service à accéder aux appareils photo système pour prendre des photos et filmer des vidéos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Cette application privilégiée ou système peut prendre des photos ou filmer des vidéos à l\'aide d\'un appareil photo système en tout temps. L\'application doit également posséder l\'autorisation android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Autoriser une application ou un service de recevoir des rappels relatifs à l\'ouverture ou à la fermeture des appareils photos."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Augmenter le volume au-dessus du niveau recommandé?\n\nL\'écoute prolongée à un volume élevé peut endommager vos facultés auditives."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utiliser le raccourci d\'accessibilité?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quand le raccourci est activé, appuyez sur les deux boutons de volume pendant trois secondes pour lancer une fonctionnalité d\'accessibilité."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Activer les fonctionnalités d\'accessibilité?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activer le raccourci pour les fonctionnalités d\'accessibilité?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Si vous maintenez enfoncées les deux touches de volume pendant quelques secondes, vous activez les fonctionnalités d\'accessibilité. Cela peut modifier le fonctionnement de votre appareil.\n\nFonctionnalités actuellement utilisées :\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPour les modifier, sélectionnez Paramètres > Accessibilité."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Activer <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activer le raccourci pour <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Si vous maintenez enfoncées les deux touches de volume pendant quelques secondes, vous activez la fonctionnalité d\'accessibilité <xliff:g id="SERVICE">%1$s</xliff:g>. Cela peut modifier le fonctionnement de votre appareil.\n\nPour attribuer ce raccourci à une autre fonctionnalité, sélectionnez Paramètres > Accessibilité."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activer"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ne pas activer"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 5fc2d6918a19..fa991a940ffd 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modifier vos paramètres audio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permet à l\'application de modifier les paramètres audio généraux, tels que le volume et la sortie audio utilisée."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"enregistrer des fichiers audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Cette application peut utiliser le micro pour enregistrer du contenu audio à tout moment."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"envoyer des commandes à la carte SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Autoriser l\'envoi de commandes à la carte SIM via l\'application. Cette fonctionnalité est très risquée."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconnaître l\'activité physique"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Cette application peut reconnaître votre activité physique."</string> <string name="permlab_camera" msgid="6320282492904119413">"prendre des photos et enregistrer des vidéos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Cette application peut utiliser l\'appareil photo pour prendre des photos et enregistrer des vidéos à tout moment."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Autoriser une application ou un service à accéder aux caméras système pour prendre des photos et enregistrer des vidéos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Cette application privilégiée ou système peut utiliser une caméra photo système pour prendre des photos et enregistrer des vidéos à tout moment. Pour cela, l\'application doit également disposer de l\'autorisation android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Autoriser une application ou un service à recevoir des rappels liés à l\'ouverture ou à la fermeture de caméras"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Augmenter le volume au dessus du niveau recommandé ?\n\nL\'écoute prolongée à un volume élevé peut endommager vos facultés auditives."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utiliser le raccourci d\'accessibilité ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quand le raccourci est activé, appuyez sur les deux boutons de volume pendant trois secondes pour démarrer une fonctionnalité d\'accessibilité."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Activer les fonctionnalités d\'accessibilité ?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activer le raccourci pour accéder aux fonctionnalités d\'accessibilité ?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Si vous appuyez sur les deux touches de volume pendant quelques secondes, vous activez des fonctionnalités d\'accessibilité. Cela peut affecter le fonctionnement de votre appareil.\n\nFonctionnalités actuellement utilisées :\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPour les modifier, accédez à Paramètres > Accessibilité."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Activer <xliff:g id="SERVICE">%1$s</xliff:g> ?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activer le raccourci <xliff:g id="SERVICE">%1$s</xliff:g> ?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Si vous appuyez sur les deux touches de volume pendant quelques secondes, vous activez la fonctionnalité d\'accessibilité <xliff:g id="SERVICE">%1$s</xliff:g>. Cela peut affecter le fonctionnement de votre appareil.\n\nPour attribuer ce raccourci à une autre fonctionnalité, accédez à Paramètres > Accessibilité."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activer"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ne pas activer"</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index a6eac55b8caa..115850a0b69b 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"cambiar a configuración de son"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite á aplicación modificar a configuración de audio global, como o volume e que altofalante se utiliza para a saída."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"gravar audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Esta aplicación pode utilizar o micrófono en calquera momento para gravar audio."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos á SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite á aplicación enviar comandos á SIM. Isto é moi perigoso."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"recoñecer actividade física"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Esta aplicación pode recoñecer a túa actividade física."</string> <string name="permlab_camera" msgid="6320282492904119413">"facer fotos e vídeos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Esta aplicación pode utilizar a cámara en calquera momento para sacar fotos e gravar vídeos."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Permitir que unha aplicación ou un servizo acceda ás cámaras do sistema para sacar fotos e gravar vídeos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Esta aplicación do sistema con privilexios pode utilizar unha cámara do sistema en calquera momento para tirar fotos e gravar vídeos. Require que a aplicación tamén teña o permiso android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permitir que unha aplicación ou servizo reciba retrochamadas cando se abran ou se pechen dispositivos con cámara."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Queres subir o volume máis do nivel recomendado?\n\nA reprodución de son a un volume elevado durante moito tempo pode provocar danos nos oídos."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Queres utilizar o atallo de accesibilidade?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Cando o atallo está activado, podes premer os dous botóns de volume durante 3 segundos para iniciar unha función de accesibilidade."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Queres activar as funcións de accesibilidade?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Queres activar as funcións de accesibilidade?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ao manter as dúas teclas de volume premidas durante uns segundos actívanse as funcións de accesibilidade. Esta acción pode cambiar o funcionamento do dispositivo.\n\nFuncións activadas actualmente:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPodes cambiar as funcións seleccionadas en Configuración > Accesibilidade."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Queres activar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Queres activar o atallo a <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ao manter as dúas teclas de volume premidas durante uns segundos actívase <xliff:g id="SERVICE">%1$s</xliff:g>, unha función de accesibilidade. Esta acción pode cambiar o funcionamento do dispositivo.\n\nPodes cambiar o uso deste atallo para outra función en Configuración > Accesibilidade."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activar"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Non activar"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 6b4bbf36a447..918a6ff33c48 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"તમારી ઑડિઓ સેટિંગ્સ બદલો"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"એપ્લિકેશનને વૈશ્વિક ઑડિઓ સેટિંગ્સને સંશોધિત કરવાની મંજૂરી આપે છે, જેમ કે વૉલ્યૂમ અને આઉટપુટ માટે કયા સ્પીકરનો ઉપયોગ કરવો."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ઑડિઓ રેકોર્ડ કરવાની"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"આ ઍપ્લિકેશન, માઇક્રોફોનનો ઉપયોગ કરીને કોઈપણ સમયે ઑડિઓ રેકોર્ડ કરી શકે છે."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"સિમ ને આદેશો મોકલો"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"એપ્લિકેશનને સિમ પરા આદેશો મોકલવાની મંજૂરી આપે છે. આ ખૂબ જ ખતરનાક છે."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"શારીરિક પ્રવૃત્તિને ઓળખો"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"આ ઍપ તમારી શારીરિક પ્રવૃત્તિને ઓળખી શકે છે."</string> <string name="permlab_camera" msgid="6320282492904119413">"ચિત્રો અને વિડિઓઝ લો"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"આ ઍપ્લિકેશન, કૅમેરાનો ઉપયોગ કરીને કોઈપણ સમયે ચિત્રો લઈ અને વિડિઓઝ રેકોર્ડ કરી શકે છે."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"ઍપ્લિકેશન અથવા સેવા ઍક્સેસને સિસ્ટમ કૅમેરાનો ઉપયોગ કરીને ફોટા અને વીડિયો લેવાની મંજૂરી આપો"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"આ વિશેષાધિકૃત અથવા સિસ્ટમ ઍપ કોઈપણ સમયે સિસ્ટમ કૅમેરાનો ઉપયોગ કરીને ફોટા લઈ અને વીડિયો રેકૉર્ડ કરી શકે છે. ઍપ દ્વારા આયોજિત કરવા માટે android.permission.CAMERAની પરવાનગી પણ જરૂરી છે"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"કૅમેરા ડિવાઇસ ચાલુ કે બંધ થવા વિશે કૉલબૅક પ્રાપ્ત કરવાની ઍપ્લિકેશન કે સેવાને મંજૂરી આપો."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ભલામણ કરેલ સ્તરની ઉપર વૉલ્યૂમ વધાર્યો?\n\nલાંબા સમય સુધી ઊંચા અવાજે સાંભળવું તમારી શ્રવણક્ષમતાને નુકસાન પહોંચાડી શકે છે."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ઍક્સેસિબિલિટી શૉર્ટકટનો ઉપયોગ કરીએ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"જ્યારે શૉર્ટકટ ચાલુ હોય, ત્યારે બન્ને વૉલ્યૂમ બટનને 3 સેકન્ડ સુધી દબાવી રાખવાથી ઍક્સેસિબિલિટી સુવિધા શરૂ થઈ જશે."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ઍક્સેસિબિલિટી સુવિધાઓ ચાલુ કરીએ?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ઍક્સેસિબિલિટી સુવિધાઓ માટે શૉર્ટકટ ચાલુ કરીએ?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"બન્ને વૉલ્યૂમ કીને થોડી સેકન્ડ સુધી દબાવી રાખવાથી ઍક્સેસિબિલિટી સુવિધાઓ ચાલુ થઈ જાય છે. આનાથી તમારા ડિવાઇસની કામ કરવાની રીત બદલાઈ શકે છે.\n\nવર્તમાન સુવિધાઓ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nતમે સેટિંગ > ઍક્સેસિબિલિટીમાં જઈને પસંદ કરેલી સુવિધાઓને બદલી શકો છો."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g>ને ચાલુ કરીએ?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> શૉર્ટકટ ચાલુ કરીએ?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"બન્ને વૉલ્યૂમ કીને થોડી સેકન્ડ સુધી દબાવી રાખવાથી ઍક્સેસિબિલિટી સુવિધા એવી <xliff:g id="SERVICE">%1$s</xliff:g> ચાલુ થઈ જાય છે. આનાથી તમારા ડિવાઇસની કામ કરવાની રીત બદલાઈ શકે છે.\n\nતમે સેટિંગ > ઍક્સેસિબિલિટીમાં જઈને આ શૉર્ટકટને બીજી સુવિધામાં બદલી શકો છો."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"ચાલુ કરો"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"ચાલુ કરશો નહીં"</string> @@ -1829,8 +1839,7 @@ <item quantity="one">%d કલાક માટે</item> <item quantity="other">%d કલાક માટે</item> </plurals> - <!-- no translation found for zen_mode_until_next_day (1403042784161725038) --> - <skip /> + <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> સુધી"</string> <string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> સુધી"</string> <string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (આગલા એલાર્મ) સુધી"</string> <string name="zen_mode_forever" msgid="740585666364912448">"તમે બંધ ન કરો ત્યાં સુધી"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 3bf9dcd90ee2..db049b19f5d5 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"अपनी ऑडियो सेटिंग बदलें"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ऐप्स को वैश्विक ऑडियो सेटिंग, जैसे वॉल्यूम और कौन-सा स्पीकर आउटपुट के लिए उपयोग किया गया, संशोधित करने देता है."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ऑडियो रिकॉर्ड करने"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"यह ऐप्लिकेशन किसी भी समय माइक्रोफ़ोन का उपयोग करके ऑडियो रिकॉर्ड कर सकता है."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"सिम पर निर्देश भेजें"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"ऐप को सिम पर निर्देश भेजने देती है. यह बहुत ही खतरनाक है."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"शरीर की गतिविधि को पहचानना"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"यह ऐप्लिकेशन आपके शरीर की गतिविधि को पहचान सकता है."</string> <string name="permlab_camera" msgid="6320282492904119413">"चित्र और वीडियो लें"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"यह ऐप्लिकेशन किसी भी समय कैमरे का उपयोग करके चित्र ले सकता है और वीडियो रिकॉर्ड कर सकता है."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"तस्वीरें और वीडियो लेने के लिए ऐप्लिकेशन या सेवा को सिस्टम के कैमरे का ऐक्सेस दें"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"यह खास सिस्टम ऐप्लिकेशन जब चाहे, तस्वीरें लेने और वीडियो रिकॉर्ड करने के लिए सिस्टम के कैमरे का इस्तेमाल कर सकता है. इसके लिए ऐप्लिकेशन को android.permission.CAMERA की अनुमति देना भी ज़रूरी है"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"डिवाइस का कैमरे चालू या बंद होने पर, किसी ऐप्लिकेशन या सेवा को कॉलबैक पाने की मंज़ूरी दें."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"वॉल्यूम को सुझाए गए स्तर से ऊपर बढ़ाएं?\n\nअत्यधिक वॉल्यूम पर ज़्यादा समय तक सुनने से आपकी सुनने की क्षमता को नुकसान हो सकता है."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"सुलभता शॉर्टकट का इस्तेमाल करना चाहते हैं?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"शॉर्टकट के चालू होने पर, दाेनाें वॉल्यूम बटन (आवाज़ कम या ज़्यादा करने वाले बटन) को तीन सेकंड तक दबाने से, सुलभता सुविधा शुरू हाे जाएगी."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"सुलभता सुविधाएं चालू करना चाहते हैं?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"क्या आप सुलभता सुविधाओं के लिए शॉर्टकट चालू करना चाहते हैं?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"आवाज़ कम और ज़्यादा करने वाले दोनों बटन को कुछ सेकंड तक दबाकर रखने से सुलभता सुविधाएं चालू हो जाती हैं. ऐसा करने से आपके डिवाइस के काम करने के तरीके में बदलाव हो सकता है.\n\nमौजूदा सुविधाएं:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nआप सेटिंग और सुलभता में जाकर चुनी हुई सुविधाएं बदल सकते हैं."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> चालू करना चाहते हैं?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"क्या आप <xliff:g id="SERVICE">%1$s</xliff:g> शॉर्टकट चालू करना चाहते हैं?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"आवाज़ कम और ज़्यादा करने वाले दोनों बटन को कुछ सेकंड तक दबाकर रखने से <xliff:g id="SERVICE">%1$s</xliff:g> चालू हो जाती है, जो एक सुलभता सुविधा है. ऐसा करने से आपके डिवाइस के काम करने के तरीके में बदलाव हो सकता है.\n\nआप सेटिंग और सुलभता में जाकर इस शॉर्टकट को किसी दूसरी सुविधा के लिए बदल सकते हैं."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"चालू करें"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"चालू न करें"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index d350d04b15f4..b9b3665b5ebe 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -435,13 +435,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"promjena postavki zvuka"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Aplikaciji omogućuje izmjenu globalnih postavki zvuka, primjerice glasnoće i zvučnika koji se upotrebljava za izlaz."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"snimanje zvuka"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Aplikacija u svakom trenutku može snimati zvuk mikrofonom."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"slati naredbe SIM-u"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućuje aplikaciji slanje naredbi SIM-u. To je vrlo opasno."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznati tjelesnu aktivnost"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ova aplikacija može prepoznati vašu tjelesnu aktivnost."</string> <string name="permlab_camera" msgid="6320282492904119413">"snimi fotografije i videozapise"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Aplikacija u svakom trenutku može snimati fotografije i videozapise fotoaparatom."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Dopustite aplikaciji ili usluzi da pristupa kamerama sustava radi snimanja fotografija i videozapisa"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ova povlaštena aplikacija ili aplikacija sustava u svakom trenutku može snimati fotografije i videozapise kamerom sustava. Aplikacija mora imati i dopuštenje android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Dopustite aplikaciji ili usluzi da prima povratne pozive o otvaranju ili zatvaranju fotoaparata."</string> @@ -1644,10 +1654,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite li pojačati zvuk iznad preporučene razine?\n\nDugotrajno slušanje glasne glazbe može vam oštetiti sluh."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li upotrebljavati prečac za pristupačnost?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kad je taj prečac uključen, pritiskom na obje tipke za glasnoću na tri sekunde pokrenut će se značajka pristupačnosti."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Želite li uključiti značajke pristupačnosti?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Želite li uključiti prečac za značajke pristupačnosti?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Značajke pristupačnosti uključuju se ako na nekoliko sekundi pritisnete obje tipke za glasnoću. Time se može promijeniti način na koji vaš uređaj radi.\n\nTrenutačne značajke:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nOdabrane značajke možete promijeniti u odjeljku Postavke > Pristupačnost."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Želite li uključiti uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Želite li uključiti prečac za uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ako na nekoliko sekundi pritisnete obje tipke za glasnoću, uključuje se značajka pristupačnosti <xliff:g id="SERVICE">%1$s</xliff:g>. Time se može promijeniti način na koji vaš uređaj radi.\n\nZnačajku na koju se taj prečac odnosi možete promijeniti u odjeljku Postavke > Pristupačnost."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Uključi"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nemoj uključiti"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 70f87b689edc..16d60dc1bf4a 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"hangbeállítások módosítása"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Lehetővé teszi az alkalmazás számára az általános hangbeállítások, például a hangerő és a használni kívánt kimeneti hangszóró módosítását."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"hanganyag rögzítése"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Az alkalmazás a mikrofon használatával bármikor készíthet hangfelvételt."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"parancsok küldése a SIM-kártyára"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Engedélyezi, hogy az alkalmazás parancsokat küldjön a SIM kártyára. Ez rendkívül veszélyes lehet."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"testmozgás felismerése"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Az alkalmazás képes felismerni a testmozgást."</string> <string name="permlab_camera" msgid="6320282492904119413">"fotók és videók készítése"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Az alkalmazás a kamera használatával bármikor készíthet fényképeket és rögzíthet videókat."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"A rendszerkamerákhoz való hozzáférés, illetve képek és videók rögzítésének engedélyezése alkalmazás vagy szolgáltatás számára"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"A rendszerkamera használatával ez az előnyben részesített vagy rendszeralkalmazás bármikor készíthet fényképeket és videókat. Az alkalmazásnak az „android.permission.CAMERA” engedéllyel is rendelkeznie kell."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Visszahívás fogadásának engedélyezése alkalmazás vagy szolgáltatás számára, ha a kamerákat megnyitják vagy bezárják."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Az ajánlott szint fölé szeretné emelni a hangerőt?\n\nHa hosszú időn át teszi ki magát nagy hangerőnek, azzal károsíthatja a hallását."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Szeretné használni a Kisegítő lehetőségek billentyűparancsot?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Ha a gyorsparancs aktív, akkor a két hangerőgomb három másodpercig tartó együttes lenyomásával kisegítő funkciót indíthat el."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Bekapcsolja a kisegítő lehetőségeket?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Bekapcsol gyorsparancsot a kisegítő lehetőségekhez?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"A kisegítő lehetőségek bekapcsolásához tartsa nyomva néhány másodpercig mindkét hangerőgombot. Ez hatással lehet az eszköz működésére.\n\nJelenlegi funkciók:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nA kiválasztott funkciókat a Beállítások > Kisegítő lehetőségek pontban módosíthatja."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Bekapcsolja a következőt: <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Bekapcsolja a(z) <xliff:g id="SERVICE">%1$s</xliff:g> szolgáltatás gyorsparancsát?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"A(z) <xliff:g id="SERVICE">%1$s</xliff:g> kisegítő lehetőség bekapcsolásához tartsa nyomva néhány másodpercig mindkét hangerőgombot. Ez hatással lehet az eszköz működésére.\n\nEzt a gyorsparancsot a Beállítások > Kisegítő lehetőségek pontban módosíthatja másik funkció használatára."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Bekapcsolom"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nem kapcsolom be"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 32ac6a8beb2a..aa4197fef8e6 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"փոխել ձեր աուդիո կարգավորումները"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Թույլ է տալիս հավելվածին փոփոխել ձայնանյութի գլոբալ կարգավորումները, ինչպես օրինակ՝ ձայնը և թե որ խոսափողն է օգտագործված արտածման համար:"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ձայնագրել աուդիո ֆայլ"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Այս հավելվածը ցանկացած պահի կարող է ձայնագրել խոսափողի օգնությամբ:"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"ուղարկել հրամաններ SIM քարտին"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Թույլ է տալիս հավելվածին հրամաններ ուղարկել SIM-ին: Սա շատ վտանգավոր է:"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"ֆիզիկական ակտիվության ճանաչում"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Հավելվածը կարող է ճանաչել ձեր ֆիզիկական ակտիվությունը:"</string> <string name="permlab_camera" msgid="6320282492904119413">"լուսանկարել և տեսանկարել"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Այս հավելվածը կարող է ցանկացած պահի լուսանկարել և տեսագրել՝ օգտագործելով տեսախցիկը:"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Թույլատրել որևէ հավելվածի կամ ծառայության օգտագործել համակարգի տեսախցիկները՝ լուսանկարելու և տեսանկարելու համար"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Այս արտոնյալ կամ համակարգային հավելվածը կարող է ցանկացած պահի լուսանկարել և տեսագրել՝ օգտագործելով համակարգի տեսախցիկները։ Հավելվածին նաև անհրաժեշտ է android.permission.CAMERA թույլտվությունը։"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Թույլատրել հավելվածին կամ ծառայությանը հետզանգեր ստանալ՝ տեսախցիկների բացվելու և փակվելու դեպքում։"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ձայնը բարձրացնե՞լ խորհուրդ տրվող մակարդակից ավել:\n\nԵրկարատև բարձրաձայն լսելը կարող է վնասել ձեր լսողությունը:"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Օգտագործե՞լ Մատչելիության դյուրանցումը։"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Հատուկ գործառույթն օգտագործելու համար սեղմեք և 3 վայրկյան սեղմած պահեք ձայնի ուժգնության երկու կոճակները, երբ գործառույթը միացված է:"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Միացնե՞լ հատուկ գործառույթները"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Միացնե՞լ հատուկ գործառույթների դյուրանցումը"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ձայնի կարգավորման երկու կոճակները մի քանի վայրկյան սեղմած պահելով կմիացնեք հատուկ գործառույթները։ Դրա արդյունքում սարքի աշխատաեղանակը կարող է փոխվել։\n\nԸնթացիկ գործառույթներ՝\n<xliff:g id="SERVICE">%1$s</xliff:g>\nԸնտրված գործառույթները փոխելու համար անցեք Կարգավորումներ > Հատուկ գործառույթներ։"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Միացնե՞լ <xliff:g id="SERVICE">%1$s</xliff:g> ծառայությունը"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Միացնե՞լ <xliff:g id="SERVICE">%1$s</xliff:g>-ի դյուրանցումը"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ձայնի կարգավորման երկու կոճակները մի քանի վայրկյան սեղմած պահելով կմիացնեք <xliff:g id="SERVICE">%1$s</xliff:g> ծառայությունը, որը հատուկ գործառույթ է։ Դրա արդյունքում սարքի աշխատաեղանակը կարող է փոխվել։\n\nԱյս դյուրանցումը մեկ այլ գործառույթով փոխելու համար անցեք Կարգավորումներ > Հատուկ գործառույթներ։"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Միացնել"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Չմիացնել"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index f8ba016e1530..3e6c23edc7e1 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ubah setelan audio Anda"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Memungkinkan aplikasi mengubah setelan audio global, misalnya volume dan pengeras suara mana yang digunakan untuk keluaran."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"rekam audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Aplikasi ini dapat merekam audio menggunakan mikrofon kapan saja."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"kirimkan perintah ke SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Mengizinkan aplikasi mengirim perintah ke SIM. Ini sangat berbahaya."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"kenali aktivitas fisik"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Aplikasi ini dapat mengenali aktivitas fisik Anda."</string> <string name="permlab_camera" msgid="6320282492904119413">"ambil gambar dan video"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Aplikasi ini dapat mengambil foto dan merekam video menggunakan kamera kapan saja."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Izinkan akses aplikasi atau layanan ke kamera sistem untuk mengambil gambar dan video"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Aplikasi sistem atau yang diberi hak istimewa ini dapat mengambil gambar dan merekam video menggunakan kamera sistem kapan saja. Mewajibkan aplikasi untuk memiliki izin android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Izinkan aplikasi atau layanan untuk menerima callback tentang perangkat kamera yang sedang dibuka atau ditutup."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Mengeraskan volume di atas tingkat yang disarankan?\n\nMendengarkan dengan volume keras dalam waktu yang lama dapat merusak pendengaran Anda."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gunakan Pintasan Aksesibilitas?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Saat pintasan aktif, menekan kedua tombol volume selama 3 detik akan memulai fitur aksesibilitas."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Aktifkan fitur aksesibilitas?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Aktifkan pintasan untuk fitur aksesibilitas?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Menahan kedua tombol volume selama beberapa detik akan mengaktifkan fitur aksesibilitas. Tindakan ini dapat mengubah cara kerja perangkat Anda.\n\nFitur saat ini:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nAnda dapat mengubah fitur yang dipilih di Setelan > Aksesibilitas."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Aktifkan <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Aktifkan pintasan <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Menahan kedua tombol volume selama beberapa detik akan mengaktifkan <xliff:g id="SERVICE">%1$s</xliff:g>, yang merupakan fitur aksesibilitas. Tindakan ini dapat mengubah cara kerja perangkat Anda.\n\nAnda dapat mengubah pintasan ini ke fitur lain di Setelan > Aksesibilitas."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktifkan"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Jangan aktifkan"</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 9278bbb5f595..272145dde05b 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"breyta hljóðstillingum"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Leyfir forriti að breyta altækum hljóðstillingum, s.s. hljóðstyrk og hvaða hátalari er notaður sem úttak."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"taka upp hljóð"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Þetta forrit getur tekið upp hljóð með hljóðnemanum hvenær sem er."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"senda skipanir til SIM-kortsins"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Leyfir forriti að senda SIM-kortinu skipanir. Þetta er mjög hættulegt."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"greina hreyfingu"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Þetta forrit getur greint hreyfingu þína."</string> <string name="permlab_camera" msgid="6320282492904119413">"taka myndir og myndskeið"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Þetta forrit getur tekið myndir og tekið upp myndskeið með myndavélinni hvenær sem er."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Veittu forriti eða þjónustu aðgang að myndavélum kerfis til að taka myndir og myndskeið"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Þetta forgangs- eða kerfisforrit hefur heimild til að taka myndir og taka upp myndskeið með myndavél kerfisins hvenær sem er. Forritið þarf einnig að vera með heimildina android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Leyfa forriti eða þjónustu að taka við svörum um myndavélar sem verið er að opna eða loka."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Hækka hljóðstyrk umfram ráðlagðan styrk?\n\nEf hlustað er á háum hljóðstyrk í langan tíma kann það að skaða heyrnina."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Viltu nota aðgengisflýtileið?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Þegar flýtileiðin er virk er kveikt á aðgengiseiginleikanum með því að halda báðum hljóðstyrkshnöppunum inni í þrjár sekúndur."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Viltu kveikja á aðgengiseiginleikum?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Kveikja á flýtileið fyrir aðgangseiginleika?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Kveikt er á aðgengiseiginleikum þegar báðum hljóðstyrkstökkunum er haldið inni í nokkrar sekúndur. Þetta getur breytt því hvernig tækið virkar.\n\nNúverandi eiginleikar:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nÞú getur breytt völdum eiginleikum í Stillingar > Aðgengi."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Viltu kveikja á <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Kveikja á flýtileið <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ef báðum hljóðstyrkstökkunum er haldið inni í nokkrar sekúndur er kveikt á aðgengiseiginleikanum <xliff:g id="SERVICE">%1$s</xliff:g>. Þetta getur breytt því hvernig tækið virkar.\n\nÞú getur breytt þessari flýtileið í annan eiginleika í Stillingar > Aðgengi."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Kveikja"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ekki kveikja"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index b962d8b7c3c8..9970915ec4b9 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modifica impostazioni audio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Consente all\'applicazione di modificare le impostazioni audio globali, come il volume e quale altoparlante viene utilizzato per l\'uscita."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"registrazione audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Questa app può registrare audio tramite il microfono in qualsiasi momento."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"invio di comandi alla SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Consente all\'app di inviare comandi alla SIM. Questo è molto pericoloso."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"riconoscimento dell\'attività fisica"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Questa app può riconoscere la tua attività fisica."</string> <string name="permlab_camera" msgid="6320282492904119413">"acquisizione di foto e video"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Questa app può scattare foto e registrare video tramite la fotocamera in qualsiasi momento."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Consenti a un\'applicazione o a un servizio di accedere alle videocamere del sistema per fare foto e video"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Questa app di sistema o con privilegi può scattare foto e registrare video tramite una videocamera di sistema in qualsiasi momento. Richiede che anche l\'app disponga dell\'autorizzazione android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Consenti a un\'applicazione o a un servizio di ricevere callback relativi all\'apertura o alla chiusura di videocamere."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vuoi aumentare il volume oltre il livello consigliato?\n\nL\'ascolto ad alto volume per lunghi periodi di tempo potrebbe danneggiare l\'udito."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Usare la scorciatoia Accessibilità?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quando la scorciatoia è attiva, puoi premere entrambi i pulsanti del volume per tre secondi per avviare una funzione di accessibilità."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Attivare le funzioni di accessibilità?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vuoi attivare la scorciatoia per le funzioni di accessibilità?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Se tieni premuti entrambi i tasti del volume per qualche secondo, vengono attivate le funzioni di accessibilità. Questa operazione potrebbe modificare il funzionamento del dispositivo.\n\nFunzioni correnti:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuoi modificare le funzioni selezionate in Impostazioni > Accessibilità."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Attivare <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vuoi attivare la scorciatoia per <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Se tieni premuti entrambi i tasti del volume per qualche secondo verrà attivata la funzione di accessibilità <xliff:g id="SERVICE">%1$s</xliff:g>. Questa operazione potrebbe modificare il funzionamento del dispositivo.\n\nPuoi associare questa scorciatoia a un\'altra funzionalità in Impostazioni > Accessibilità."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Attiva"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Non attivare"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 821e4fd0acbf..e8413d66a5fe 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -438,13 +438,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"שנה את הגדרות האודיו שלך"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"מאפשר לאפליקציה לשנות הגדרות אודיו גלובליות כמו עוצמת קול ובחירת הרמקול המשמש לפלט."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"הקלט אודיו"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"אפליקציה זו יכולה להשתמש במיקרופון כדי להקליט אודיו בכל עת."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"שליחת פקודות אל ה-SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"מאפשרת ליישום לשלוח פקודות ל-SIM. זוהי הרשאה מסוכנת מאוד."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"זיהוי הפעילות הגופנית"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"האפליקציה מזהה את הפעילות הגופנית שלך."</string> <string name="permlab_camera" msgid="6320282492904119413">"צלם תמונות וסרטונים"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"אפליקציה זו יכולה להשתמש במצלמה כדי לצלם תמונות ולהקליט סרטונים בכל עת."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"הרשאת גישה לאפליקציה או לשירות למצלמות המערכת כדי לצלם תמונות וסרטונים"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"אפליקציה זו בעלת ההרשאות, או אפליקציית המערכת הזו, יכולה לצלם תמונות ולהקליט סרטונים באמצעות מצלמת מערכת בכל זמן. בנוסף, לאפליקציה נדרשת ההרשאה android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"אפליקציה או שירות יוכלו לקבל קריאות חוזרות (callback) כשמכשירי מצלמה ייפתחו או ייסגרו."</string> @@ -1666,10 +1676,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"האם להעלות את עוצמת הקול מעל לרמה המומלצת?\n\nהאזנה בעוצמת קול גבוהה למשכי זמן ממושכים עלולה לפגוע בשמיעה."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"להשתמש בקיצור הדרך לתכונת הנגישות?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"כשקיצור הדרך מופעל, לחיצה על שני לחצני עוצמת הקול למשך שלוש שניות מפעילה את תכונת הנגישות."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"להפעיל את תכונות הנגישות?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"האם להפעיל את מקש הקיצור לתכונות הנגישות?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"לחיצה ארוכה על שני לחצני עוצמת הקול למשך מספר שניות מפעילה את תכונות הנגישות. בעקבות זאת, ייתכן שאופן הפעולה של המכשיר ישתנה.\n\nהתכונות הנוכחיות:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nניתן לשנות תכונות נבחרות ב\'הגדרות\' > \'נגישות\'."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"להפעיל את <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"האם להפעיל את מקש הקיצור של <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ניתן ללחוץ על שני מקשי עוצמת הקול למשך מספר שניות כדי להפעיל את <xliff:g id="SERVICE">%1$s</xliff:g>, תכונת נגישות. בעקבות זאת, ייתכן שאופן הפעולה של המכשיר ישתנה.\n\nאפשר לשנות את מקשי הקיצור האלה לתכונה נוספת ב\'הגדרות\' > \'נגישות\'."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"אני רוצה להפעיל"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"לא להפעיל"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 4b48495a562e..012ee3f352fd 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"音声設定の変更"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"音声全般の設定(音量、出力に使用するスピーカーなど)の変更をアプリに許可します。"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"録音"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"このアプリは、いつでもマイクを使用して録音できます。"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIMへのコマンド送信"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIMにコマンドを送信することをアプリに許可します。この許可は非常に危険です。"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"身体活動の認識"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"このアプリで身体活動が認識されるようにします。"</string> <string name="permlab_camera" msgid="6320282492904119413">"写真と動画の撮影"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"このアプリは、いつでもカメラを使用して写真や動画を撮影できます。"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"写真と動画を撮影するには、システムカメラへのアクセスをアプリまたはサービスに許可してください"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"権限を付与されたこのアプリまたはシステムアプリは、いつでもシステムカメラを使用して写真と動画を撮影できます。アプリには android.permission.CAMERA 権限も必要です"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"カメラデバイスが起動または終了したときにコールバックを受け取ることを、アプリまたはサービスに許可してください。"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"推奨レベルを超えるまで音量を上げますか?\n\n大音量で長時間聞き続けると、聴力を損なう恐れがあります。"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ユーザー補助機能のショートカットの使用"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ショートカットが ON の場合、両方の音量ボタンを 3 秒ほど長押しするとユーザー補助機能が起動します。"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ユーザー補助機能を ON にしますか?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ユーザー補助機能のショートカットを ON にしますか?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"音量大と音量小の両方のボタンを数秒ほど長押しすると、ユーザー補助機能が ON になります。この機能が ON になると、デバイスの動作が変わることがあります。\n\n現在の機能:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n選択した機能は [設定] > [ユーザー補助] で変更できます。"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> を ON にしますか?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> のショートカットを ON にしますか?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"音量大と音量小の両方のボタンを数秒ほど長押しすると、ユーザー補助機能の <xliff:g id="SERVICE">%1$s</xliff:g> が ON になります。この機能が ON になると、デバイスの動作が変わることがあります。\n\nこのショートカットは [設定] > [ユーザー補助] で別の機能に変更できます。"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"ON にする"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"ON にしない"</string> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index cdac64757058..f6e8f5d52ef6 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"თქვენი აუდიო პარამეტრების შეცვლა"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"აპს შეეძლება აუდიოს გლობალური პარამეტრების შეცვლა. მაგ.: ხმის სიმაღლე და რომელი დინამიკი გამოიყენება სიგნალის გამოსტანად."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"აუდიოს ჩაწერა"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"ამ აპს ნებისმიერ დროს შეუძლია მიკროფონით აუდიოს ჩაწერა."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"ბრძანებების SIM-ზე გაგზავნა"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"აპისთვის ნების დართვა გაუგზავნოს ბრძანებები SIM-ბარათს. ეს ძალიან საშიშია."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"ფიზიკური აქტივობის ამოცნობა"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"ამ აპს შეუძლია თქვენი ფიზიკური აქტივობის ამოცნობა."</string> <string name="permlab_camera" msgid="6320282492904119413">"სურათებისა და ვიდეოების გადაღება"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"ამ აპს ნებისმიერ დროს შეუძლია კამერით სურათების გადაღება და ვიდეოების ჩაწერა."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"ნება დაერთოს აპლიკაციას ან სერვისს, ჰქონდეს წვდომა სისტემის კამერებზე სურათების და ვიდეოების გადასაღებად"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"ამ პრივილეგირებულ ან სისტემის აპს შეუძლია ფოტოების გადაღება და ვიდეოების ჩაწერა ნებისმიერ დროს სისტემის კამერის გამოყენებით. საჭიროა, რომ აპს ჰქოდეს android.permission.CAMERA ნებართვაც"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ნება დაერთოს აპლიკაციას ან სერვისს, მიიღოს გადმორეკვები კამერის მოწყობილობის გახსნის ან დახურვისას."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"გსურთ ხმის რეკომენდებულ დონეზე მაღლა აწევა?\n\nხანგრძლივად ხმამაღლა მოსმენით შესაძლოა სმენადობა დაიზიანოთ."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"გსურთ მარტივი წვდომის მალსახმობის გამოყენება?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"თუ მალსახმობი ჩართულია, ხმის ორივე ღილაკზე 3 წამის განმავლობაში დაჭერით მარტივი წვდომის ფუნქცია ჩაირთვება."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"გსურთ, ჩართოთ მარტივი წვდომის ფუნქციები?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ჩაირთოს მარტივი წვდომის ფუნქციების მალსახმობი?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ხმის ორივე ღილაკზე ხანგრძლივად დაჭერა რამდენიმე წამის განმავლობაში ჩართავს მარტივი წვდომის ფუნქციებს. ამ ქმედებამ შეიძლება შეცვალოს თქვენი მოწყობილობის მუშაობის პრინციპი.\n\nამჟამინდელი ფუნქციები:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nარჩეული ფუნქციების შეცვლა შესაძლებელია აქ: პარამეტრები > მარტივი წვდომა."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"ჩაირთოს <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"ჩაირთოს <xliff:g id="SERVICE">%1$s</xliff:g>-ის მალსახმობი?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ხმის ორივე ღილაკზე რამდენიმე წამის განმავლობაში დაჭერით ჩაირთვება <xliff:g id="SERVICE">%1$s</xliff:g>, რომელიც მარტივი წვდომის ფუნქციაა. ამან შეიძლება შეცვალოს თქვენი მოწყობილობის მუშაობის პრინციპი.\n\nამ მალსახმობის შეცვლა სხვა ფუნქციით შეგიძლიათ აქ: პარამეტრები > აპები."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"ჩართვა"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"არ ჩაირთოს"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 608ed1e0186b..b0d363c109a5 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"аудио параметрлерін өзгерту"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Қолданбаға дыбыс қаттылығы және аудио шығыс үндеткішін таңдау сияқты жаһандық аудио параметрлерін өзгерту мүмкіндігін береді."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"аудио жазу"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Бұл қолданба кез келген уақытта микрофон арқылы аудиомазмұн жаза алады."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM картасына пәрмендер жіберу"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Қолданбаға SIM картасына пәрмен жіберу мүмкіндігін береді. Бұл өте қауіпті."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"физикалық әрекетті тану"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Бұл қолданба физикалық әрекетті тани алады."</string> <string name="permlab_camera" msgid="6320282492904119413">"фотосурет жасау және бейне жазу"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Бұл қолданба кез келген уақытта камерамен суретке түсіруі және бейнелерді жазуы мүмкін."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Суретке немесе бейнеге түсіру үшін қолданбаға немесе қызметке жүйелік камераларды пайдалануға рұқсат беру"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Осы айрықша немесе жүйе қолданбасы кез келген уақытта жүйелік камера арқылы суретке не бейнеге түсіре алады. Қолданбаға android.permission.CAMERA рұқсаты қажет болады."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Қолданбаға не қызметке ашылып не жабылып жатқан камера құрылғылары туралы кері шақыру алуға рұқсат ету"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Дыбыс деңгейін ұсынылған деңгейден көтеру керек пе?\n\nЖоғары дыбыс деңгейінде ұзақ кезеңдер бойы тыңдау есту қабілетіңізге зиян тигізуі мүмкін."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Арнайы мүмкіндік төте жолын пайдалану керек пе?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Түймелер тіркесімі қосулы кезде, екі дыбыс түймесін 3 секунд басып тұрсаңыз, \"Арнайы мүмкіндіктер\" функциясы іске қосылады."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Арнайы мүмкіндіктер іске қосылсын ба?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Арнайы мүмкіндіктердің жылдам пәрмені іске қосылсын ба?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Дыбыс деңгейі пернелерін бірнеше секунд басып тұрсаңыз, арнайы мүмкіндіктер іске қосылады. Бұл – құрылғының жұмысына әсер етуі мүмкін.\n\nҚазіргі функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТаңдалған функцияларды \"Параметрлер > Арнайы мүмкіндіктер\" бөлімінен өзгерте аласыз."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> қосылсын ба?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> жылдам пәрмені іске қосылсын ба?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Дыбыс деңгейі пернелерін бірнеше секунд басып тұрсаңыз, <xliff:g id="SERVICE">%1$s</xliff:g> арнайы қызметі іске қосылады. Бұл – құрылғының жүмысына әсер етуі мүмкін.\n\nБұл таңбашаны басқа функцияға \"Параметрлер > Арнайы мүмкіндіктер\" бөлімінен өзгерте аласыз."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Қосылсын"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Қосылмасын"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 14b0189a4aa0..a16251dd0938 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ប្ដូរការកំណត់អូឌីយូរបស់អ្នក"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ឲ្យកម្មវិធីកែការកំណត់សំឡេងសកល ដូចជាកម្រិតសំឡេង និងអូប៉ាល័រដែលបានប្រើសម្រាប់លទ្ធផល។"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ថតសំឡេង"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"កម្មវិធីនេះអាចថតសំឡេងដោយប្រើមីក្រូហ្វូនបានគ្រប់ពេល។"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"ផ្ញើពាក្យបញ្ជាទៅស៊ីមកាត"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"ឲ្យកម្មវិធីផ្ញើពាក្យបញ្ជាទៅស៊ីមកាត។ វាគ្រោះថ្នាក់ណាស់។"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"ស្គាល់សកម្មភាពរាងកាយ"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"កម្មវិធីនេះអាចស្គាល់សកម្មភាពរាងកាយរបស់អ្នក។"</string> <string name="permlab_camera" msgid="6320282492904119413">"ថតរូប និងវីដេអូ"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"កម្មវិធីនេះអាចថតរូប និងថតវីដេអូ ដោយប្រើកាមេរ៉ាបានគ្រប់ពេល។"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"អនុញ្ញាតឱ្យកម្មវិធី ឬសេវាកម្មចូលប្រើកាមេរ៉ាប្រព័ន្ធ ដើម្បីថតរូប និងថតវីដេអូ"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"កម្មវិធីប្រព័ន្ធ ឬកម្មវិធីដែលមានសិទ្ធិអនុញ្ញាតនេះអាចថតរូប និងថតវីដេអូ ដោយប្រើកាមេរ៉ាប្រព័ន្ធបានគ្រប់ពេល។ តម្រូវឱ្យមានការអនុញ្ញាត android.permission.CAMERA ដើម្បីឱ្យកម្មវិធីអាចធ្វើសកម្មភាពបានផងដែរ"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"អនុញ្ញាតឱ្យកម្មវិធី ឬសេវាកម្មទទួលការហៅត្រឡប់វិញអំពីកាមេរ៉ាដែលកំពុងបិទ ឬបើក។"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"បង្កើនកម្រិតសំឡេងលើសពីកម្រិតបានផ្ដល់យោបល់?\n\nការស្ដាប់នៅកម្រិតសំឡេងខ្លាំងយូរអាចធ្វើឲ្យខូចត្រចៀក។"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ប្រើប្រាស់ផ្លូវកាត់ភាពងាយស្រួល?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"នៅពេលបើកផ្លូវកាត់ ការចុចប៊ូតុងកម្រិតសំឡេងទាំងពីររយៈពេល 3 វិនាទីនឹងចាប់ផ្តើមមុខងារភាពងាយប្រើ។"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"បើកមុខងារភាពងាយប្រើឬ?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"បើកផ្លូវកាត់សម្រាប់មុខងារភាពងាយស្រួលឬ?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ការសង្កត់គ្រាប់ចុចកម្រិតសំឡេងទាំងពីរឱ្យជាប់រយៈពេលពីរបីវិនាទីនឹងបើកមុខងារភាពងាយប្រើ។ ការធ្វើបែបនេះអាចផ្លាស់ប្ដូររបៀបដែលឧបករណ៍របស់អ្នកដំណើរការ។\n\nមុខងារបច្ចុប្បន្ន៖\n<xliff:g id="SERVICE">%1$s</xliff:g>\nអ្នកអាចប្ដូរមុខងារដែលបានជ្រើសរើសនៅក្នុងការកំណត់ > ភាពងាយស្រួល។"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"បើក <xliff:g id="SERVICE">%1$s</xliff:g> ឬ?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"បើកផ្លូវកាត់ <xliff:g id="SERVICE">%1$s</xliff:g> ឬ?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ការសង្កត់គ្រាប់ចុចកម្រិតសំឡេងទាំងពីរឱ្យជាប់រយៈពេលពីរបីវិនាទីនឹងបើក <xliff:g id="SERVICE">%1$s</xliff:g> ដែលជាមុខងារភាពងាយប្រើ។ ការធ្វើបែបនេះអាចផ្លាស់ប្ដូររបៀបដែលឧបករណ៍របស់អ្នកដំណើរការ។\n\nអ្នកអាចប្ដូរផ្លូវកាត់នេះទៅមុខងារផ្សេងទៀតនៅក្នុងការកំណត់ > ភាពងាយស្រួល។"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"បើក"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"កុំបើក"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 0fe1c24bddf0..58ecb627de1d 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ನಿಮ್ಮ ಆಡಿಯೊ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಬದಲಾಯಿಸಿ"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ವಾಲ್ಯೂಮ್ ರೀತಿಯ ಮತ್ತು ಔಟ್ಪುಟ್ಗಾಗಿ ಯಾವ ಸ್ಪೀಕರ್ ಬಳಸಬೇಕು ಎಂಬ ರೀತಿಯ ಜಾಗತಿಕ ಆಡಿಯೊ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಮಾರ್ಪಡಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ಆಡಿಯೊ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಮೈಕ್ರೋಫೋನ್ ಬಳಸುವ ಮೂಲಕ ಯಾವುದೇ ಸಮಯದಲ್ಲಾದರೂ ಆಡಿಯೊ ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"ಸಿಮ್ಗೆ ಆಜ್ಞೆಗಳನ್ನು ಕಳುಹಿಸಿ"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"ಸಿಮ್ ಗೆ ಆದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಇದು ತುಂಬಾ ಅಪಾಯಕಾರಿ."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"ದೈಹಿಕ ಚಟುವಟಿಕೆಯನ್ನು ಗುರುತಿಸಿ"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"ಈ ಆ್ಯಪ್ ನಿಮ್ಮ ದೈಹಿಕ ಚಟುವಟಿಕೆಯನ್ನು ಗುರುತಿಸಬಹುದು."</string> <string name="permlab_camera" msgid="6320282492904119413">"ಚಿತ್ರಗಳು ಮತ್ತು ವೀಡಿಯೊಗಳನ್ನು ಸೆರೆಹಿಡಿಯಿರಿ"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಯಾವ ಸಮಯದಲ್ಲಾದರೂ ಕ್ಯಾಮರಾ ಬಳಸಿಕೊಂಡು ಚಿತ್ರಗಳು ಮತ್ತು ವಿಡಿಯೋಗಳನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"ಫೋಟೋಗಳು ಮತ್ತು ವೀಡಿಯೊಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳಲು ಸಿಸ್ಟಂ ಕ್ಯಾಮರಾಗಳಿಗೆ ಅಪ್ಲಿಕೇಶನ್ ಅಥವಾ ಸೇವಾ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"ಈ ವಿಶೇಷ ಅಥವಾ ಸಿಸ್ಟಂ ಆ್ಯಪ್, ಯಾವುದೇ ಸಮಯದಲ್ಲಾದರೂ ಸಿಸ್ಟಂ ಕ್ಯಾಮರಾವನ್ನು ಬಳಸಿಕೊಂಡು ಫೋಟೋಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳಬಹುದು ಮತ್ತು ವೀಡಿಯೋಗಳನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು. ಆ್ಯಪ್ಗೆ android.permission.CAMERA ಅನುಮತಿಯ ಅಗತ್ಯವಿರುತ್ತದೆ"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ಕ್ಯಾಮರಾ ಸಾಧನಗಳನ್ನು ತೆರೆಯುತ್ತಿರುವ ಅಥವಾ ಮುಚ್ಚುತ್ತಿರುವ ಕುರಿತು ಕಾಲ್ಬ್ಯಾಕ್ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಆ್ಯಪ್ ಅಥವಾ ಸೇವೆಗೆ ಅನುಮತಿಸಿ."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ವಾಲ್ಯೂಮ್ ಅನ್ನು ಶಿಫಾರಸು ಮಾಡಲಾದ ಮಟ್ಟಕ್ಕಿಂತಲೂ ಹೆಚ್ಚು ಮಾಡುವುದೇ?\n\nದೀರ್ಘ ಅವಧಿಯವರೆಗೆ ಹೆಚ್ಚಿನ ವಾಲ್ಯೂಮ್ನಲ್ಲಿ ಆಲಿಸುವುದರಿಂದ ನಿಮ್ಮ ಆಲಿಸುವಿಕೆ ಸಾಮರ್ಥ್ಯಕ್ಕೆ ಹಾನಿಯುಂಟು ಮಾಡಬಹುದು."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ಪ್ರವೇಶಿಸುವಿಕೆ ಶಾರ್ಟ್ಕಟ್ ಬಳಸುವುದೇ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ಶಾರ್ಟ್ಕಟ್ ಆನ್ ಆಗಿರುವಾಗ, ಎರಡೂ ವಾಲ್ಯೂಮ್ ಬಟನ್ಗಳನ್ನು 3 ಸೆಕೆಂಡುಗಳ ಕಾಲ ಒತ್ತಿದರೆ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯವೊಂದು ಪ್ರಾರಂಭವಾಗುತ್ತದೆ."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಆನ್ ಮಾಡಬೇಕೇ?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯಗಳಿಗಾಗಿ ಶಾರ್ಟ್ಕಟ್ ಆನ್ ಮಾಡಬೇಕೇ?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ಎರಡೂ ವಾಲ್ಯೂಮ್ ಕೀಗಳನ್ನು ಕೆಲವು ಸೆಕೆಂಡುಗಳ ಕಾಲ ಹಿಡಿದಿಟ್ಟುಕೊಳ್ಳುವುದರಿಂದ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯಗಳು ಆನ್ ಆಗುತ್ತವೆ. ಇದು ನಿಮ್ಮ ಸಾಧನವು ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ಬದಲಾಯಿಸಬಹುದು.\n\n ಪ್ರಸ್ತುತ ವೈಶಿಷ್ಟ್ಯಗಳು:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nಸೆಟ್ಟಿಂಗ್ಗಳು ಮತ್ತು ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿಯಲ್ಲಿ ಆಯ್ದ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ನೀವು ಬದಲಾಯಿಸಬಹುದು."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ಅನ್ನು ಆನ್ ಮಾಡುವುದೇ?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"ಶಾರ್ಟ್ಕಟ್ <xliff:g id="SERVICE">%1$s</xliff:g>ಆನ್ ಮಾಡಬೇಕೇ?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ಎರಡೂ ವಾಲ್ಯೂಮ್ ಕೀಗಳನ್ನು ಕೆಲವು ಸೆಕೆಂಡುಗಳ ಕಾಲ ಹಿಡಿದಿಟ್ಟುಕೊಳ್ಳುವುದರಿಂದ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯವಾದ <xliff:g id="SERVICE">%1$s</xliff:g> ಆನ್ ಆಗುತ್ತದೆ. ಇದು ನಿಮ್ಮ ಸಾಧನವು ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ಬದಲಾಯಿಸಬಹುದು.\n\nನೀವು ಈ ಶಾರ್ಟ್ಕಟ್ ಅನ್ನು ಸೆಟ್ಟಿಂಗ್ಗಳು ಮತ್ತು ಅಕ್ಸೆಸಿಬಿಲಿಟಿಯಲ್ಲಿನ ಮತ್ತೊಂದು ವೈಶಿಷ್ಟ್ಯಕ್ಕೆ ಬದಲಾಯಿಸಬಹುದು."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"ಆನ್ ಮಾಡಿ"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"ಆನ್ ಮಾಡಬೇಡಿ"</string> @@ -1829,8 +1839,7 @@ <item quantity="one">%d ಗಂಟೆಗೆ</item> <item quantity="other">%d ಗಂಟೆಗೆ</item> </plurals> - <!-- no translation found for zen_mode_until_next_day (1403042784161725038) --> - <skip /> + <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ವರೆಗೆ"</string> <string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ವರೆಗೆ"</string> <string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ವರೆಗೆ (ಮುಂದಿನ ಅಲಾರಮ್)"</string> <string name="zen_mode_forever" msgid="740585666364912448">"ನೀವು ಆಫ್ ಮಾಡುವವರೆಗೆ"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index e3406c1bf003..394bd218dc6a 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"오디오 설정 변경"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"앱이 음량이나 출력을 위해 사용하는 스피커 등 전체 오디오 설정을 변경할 수 있도록 허용합니다."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"오디오 녹음"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"이 앱은 언제든지 마이크를 사용하여 오디오를 녹음할 수 있습니다."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM 카드로 명령 전송"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"앱이 SIM에 명령어를 전송할 수 있도록 허용합니다. 이 기능은 매우 신중히 허용해야 합니다."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"신체 활동 확인"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"이 앱에서 내 신체 활동을 확인할 수 있습니다."</string> <string name="permlab_camera" msgid="6320282492904119413">"사진과 동영상 찍기"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"이 앱은 언제든지 카메라를 사용하여 사진을 촬영하고 동영상을 녹화할 수 있습니다."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"사진 및 동영상 촬영을 위해 애플리케이션 또는 서비스에서 시스템 카메라에 액세스하도록 허용"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"이 권한이 있는 시스템 앱은 언제든지 시스템 카메라를 사용하여 사진을 촬영하고 동영상을 녹화할 수 있습니다. 또한 앱에 android.permission.CAMERA 권한이 필요합니다."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"애플리케이션 또는 서비스에서 카메라 기기 열림 또는 닫힘에 대한 콜백을 수신하도록 허용"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"권장 수준 이상으로 볼륨을 높이시겠습니까?\n\n높은 볼륨으로 장시간 청취하면 청력에 손상이 올 수 있습니다."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"접근성 단축키를 사용하시겠습니까?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"단축키가 사용 설정된 경우 볼륨 버튼 두 개를 동시에 3초간 누르면 접근성 기능이 시작됩니다."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"접근성 기능을 사용하시겠습니까?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"접근성 기능 바로가기를 사용 설정하시겠습니까?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"볼륨 키 2개를 몇 초 동안 길게 누르면 접근성 기능이 사용 설정됩니다. 이때 기기 작동 방식이 달라질 수 있습니다.\n\n현재 기능:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n설정 > 접근성에서 선택한 기능을 변경할 수 있습니다."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g>을(를) 사용하시겠습니까?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> 바로가기를 사용 설정하시겠습니까?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"볼륨 키 2개를 몇 초 동안 길게 누르면 <xliff:g id="SERVICE">%1$s</xliff:g> 접근성 기능이 사용 설정됩니다. 이렇게 되면 기기 작동 방식이 달라질 수 있습니다.\n\n설정 > 접근성에서 이 단축키를 다른 기능으로 변경할 수 있습니다."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"사용"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"사용 안 함"</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index 3268d16c1df9..0774f6a5e3b7 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"аудио жөндөөлөрүңүздү өзгөртүңүз"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Колдонмого үн деңгээли жана кайсы динамик аркылуу үн чыгарылышы керек сыяктуу түзмөктүн аудио тууралоолорун өзгөртүүгө уруксат берет."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"аудио жаздыруу"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Бул колдонмо каалаган убакта микрофон менен аудио файлдарды жаздыра алат."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM-картага буйруктарды жөнөтүү"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Колдонмого SIM-картага буйруктарды жөнөтүү мүмкүнчүлүгүн берет. Бул абдан кооптуу."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"кыймыл-аракетти аныктоо"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Бул колдонмо кыймыл-аракетиңизди аныктап турат."</string> <string name="permlab_camera" msgid="6320282492904119413">"сүрөт жана видео тартуу"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Бул колдонмо каалаган убакта камера менен сүрөт же видеолорду тарта алат."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Сүрөткө тартып, видеолорду жаздыруу үчүн бул колдонмого же кызматка тутумдун камерасын колдонууга уруксат берүү"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Бул артыкчылыктуу тутум колдонмосу тутумдун камерасын каалаган убакта колдонуп, сүрөткө тартып, видео жаздыра алат. Ошондой эле колдонмого android.permission.CAMERA уруксатын берүү керек"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Колдонмого же кызматка камера ачылып же жабылып жатканда чалууларды кабыл алууга уруксат берүү."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Сунушталган деңгээлден да катуулатып уккуңуз келеби?\n\nМузыканы узакка чейин катуу уксаңыз, угууңуз начарлап кетиши мүмкүн."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Ыкчам иштетесизби?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Атайын мүмкүнчүлүктөр функциясын пайдалануу үчүн ал күйгүзүлгөндө, үндү катуулатып/акырындаткан эки баскычты тең 3 секунддай коё бербей басып туруңуз."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Атайын мүмкүнчүлүктөрдү иштетесизби?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Атайын мүмкүнчүлүктөрдүн ыкчам баскычын иштетесизби?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Атайын мүмкүнчүлүктөр функциясын иштетүү үчүн, үндү чоңойтуп/кичирейтүү баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nУчурдагы функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТандалган функцияларды өзгөртүү үчүн, Жөндөөлөр > Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> күйгүзүлсүнбү?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> ыкчам баскычын иштетесизби?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"<xliff:g id="SERVICE">%1$s</xliff:g> кызматын иштетүү үчүн, үндү чоңойтуп/кичирейтүү баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nБаскычтардын ушул айкалышын башка функцияга дайындоо үчүн, Жөндөөлөр > Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ооба"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Жок"</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index d22ce0c1f964..875d6817e791 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ປ່ຽນການຕັ້ງຄ່າສຽງຂອງທ່ານ"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ອະນຸຍາດໃຫ້ແອັບຯແກ້ໄຂການຕັ້ງຄ່າສຽງສ່ວນກາງ ເຊັ່ນ: ລະດັບສຽງ ແລະລຳໂພງໃດທີ່ຖືກໃຊ້ສົ່ງສຽງອອກ."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ບັນທຶກສຽງ"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"This app can record audio using the microphone at any time."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"ສົ່ງຄຳສັ່ງຫາ SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"ອະນຸຍາດໃຫ້ແອັບຯສົ່ງຄຳສັ່ງຫາ SIM. ສິ່ງນີ້ອັນຕະລາຍຫຼາຍ."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"ຈຳແນກກິດຈະກຳທາງກາຍ"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"ແອັບນີ້ສາມາດຈັດລະບຽບການເຄື່ອນໄຫວທາງກາຍຂອງທ່ານໄດ້."</string> <string name="permlab_camera" msgid="6320282492904119413">"ຖ່າຍຮູບ ແລະວິດີໂອ"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"This app can take pictures and record videos using the camera at any time."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນ ຫຼື ບໍລິການເຂົ້າເຖິງກ້ອງຂອງລະບົບໄດ້ເພື່ອຖ່າຍຮູບ ແລະ ວິດີໂອ"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"ສິດ ຫຼື ແອັບລະບົບນີ້ສາມາດຖ່າຍຮູບ ແລະ ບັນທຶກວິດີໂອໂດຍໃຊ້ກ້ອງຂອງລະບົບຕອນໃດກໍໄດ້. ຕ້ອງໃຊ້ສິດອະນຸຍາດ android.permission.CAMERA ໃຫ້ແອັບຖືນຳ"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນ ຫຼື ບໍລິການຮັບການເອີ້ນກັບກ່ຽວກັບອຸປະກອນກ້ອງຖືກເປີດ ຫຼື ປິດໄດ້."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ເພີ່ມລະດັບສຽງໃຫ້ເກີນກວ່າລະດັບທີ່ແນະນຳບໍ?\n\nການຮັບຟັງສຽງໃນລະດັບທີ່ສູງເປັນໄລຍະເວລາດົນອາດເຮັດໃຫ້ການຟັງຂອງທ່ານມີບັນຫາໄດ້."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ໃຊ້ປຸ່ມລັດການຊ່ວຍເຂົ້າເຖິງບໍ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ເມື່ອເປີດໃຊ້ທາງລັດແລ້ວ, ການກົດປຸ່ມລະດັບສຽງທັງສອງຄ້າງໄວ້ 3 ວິນາທີຈະເປັນການເລີ່ມຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ເປີດໃຊ້ຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງບໍ?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ເປີດໃຊ້ທາງລັດສຳລັບຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງບໍ?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ກົດປຸ່ມລະດັບສຽງທັງສອງຄ້າງໄວ້ສອງສາມວິນາທີເພື່ອເປີດໃຊ້ຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ. ນີ້ອາດປ່ຽນວິທີການເຮັດວຽກຂອງອຸປະກອນທ່ານ.\n\nຄຸນສົມບັດປັດຈຸບັນ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nທ່ານສາມາດປ່ຽນຄຸນສົມບັດທີ່ເລືອກໄດ້ໃນການຕັ້ງຄ່າ > ການຊ່ວຍເຂົ້າເຖິງ."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"ເປີດໃຊ້ <xliff:g id="SERVICE">%1$s</xliff:g> ບໍ?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"ເປີດໃຊ້ທາງລັດ <xliff:g id="SERVICE">%1$s</xliff:g> ບໍ?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ກົດປຸ່ມລະດັບສຽງທັງສອງຄ້າງໄວ້ສອງສາມວິນາທີເພື່ອເປີດໃຊ້ <xliff:g id="SERVICE">%1$s</xliff:g>, ຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ. ນີ້ອາດປ່ຽນວິທີການເຮັດວຽກຂອງອຸປະກອນທ່ານ.\n\nທ່ານສາມາດປ່ຽນທາງລັດນີ້ເປັນຄຸນສົມບັດອື່ນໄດ້ໃນການຕັ້ງຄ່າ > ການຊ່ວຍເຂົ້າເຖິງ."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"ເປີດໃຊ້"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"ບໍ່ເປີດ"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index ff687c575396..a3e83918702e 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -438,13 +438,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"keisti garso nustatymus"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Leidžiama programai keisti visuotinius garso nustatymus, pvz., garsumą ir tai, kuris garsiakalbis naudojamas išvesčiai."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"įrašyti garsą"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ši programa gali bet kada įrašyti garsą naudodama mikrofoną."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"siųsti komandas į SIM kortelę"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Programai leidžiama siųsti komandas į SIM kortelę. Tai labai pavojinga."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"atpažinti fizinę veiklą"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ši programa gali atpažinti jūsų fizinę veiklą."</string> <string name="permlab_camera" msgid="6320282492904119413">"fotografuoti ir filmuoti"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Ši programa gali bet kada fotografuoti ir įrašyti vaizdo įrašų naudodama fotoaparatą."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Suteikti programai arba paslaugai prieigą prie sistemos fotoaparatų, kad būtų galima daryti nuotraukas ir įrašyti vaizdo įrašus"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ši privilegijuota arba sistemos programa gali daryti nuotraukas ir įrašyti vaizdo įrašus naudodama sistemos fotoaparatą bet kuriuo metu. Programai taip pat būtinas leidimas „android.permission.CAMERA“"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Leisti programai ar paslaugai sulaukti atgalinio skambinimo, kai atidaromas ar uždaromas fotoaparatas."</string> @@ -1666,10 +1676,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Padidinti garsą daugiau nei rekomenduojamas lygis?\n\nIlgai klausydami dideliu garsu galite pažeisti klausą."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Naudoti spartųjį pritaikymo neįgaliesiems klavišą?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kai spartusis klavišas įjungtas, paspaudus abu garsumo mygtukus ir palaikius 3 sekundes bus įjungta pritaikymo neįgaliesiems funkcija."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Įjungti pritaikymo neįgaliesiems funkcijas?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Įjungti pritaikymo neįgaliesiems funkcijų spartųjį klavišą?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Paspaudus abu garsumo klavišus ir palaikius kelias sekundes įjungiamos pritaikymo neįgaliesiems funkcijos. Tai gali pakeisti įrenginio veikimą.\n\nDabartinės funkcijos:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPasirinktas funkcijas galite pakeisti skiltyje „Nustatymai“ > „Pritaikomumas“."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Įjungti „<xliff:g id="SERVICE">%1$s</xliff:g>“?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Įjungti „<xliff:g id="SERVICE">%1$s</xliff:g>“ spartųjį klavišą?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Paspaudus abu garsumo klavišus ir palaikius kelias sekundes įjungiama pritaikymo neįgaliesiems funkcija „<xliff:g id="SERVICE">%1$s</xliff:g>“. Tai gali pakeisti įrenginio veikimą.\n\nGalite pakeisti šį spartųjį klavišą į kitą funkciją skiltyje „Nustatymai“ > „Pritaikomumas“."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Įjungti"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Neįjungti"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 2cb6058c1ef7..f086662668f0 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -435,13 +435,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"mainīt audio iestatījumus"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Ļauj lietotnei mainīt globālos audio iestatījumus, piemēram, skaļumu un izejai izmantoto skaļruni."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ierakstīt audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Šī lietotne jebkurā brīdī var ierakstīt audio, izmantojot mikrofonu."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"Sūtīt komandas SIM kartei"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Ļauj lietotnei sūtīt komandas uz SIM karti. Tas ir ļoti bīstami!"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"noteikt fiziskās aktivitātes"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Šī lietotne var noteikt jūsu fiziskās aktivitātes."</string> <string name="permlab_camera" msgid="6320282492904119413">"uzņemt attēlus un videoklipus"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Šī lietotne jebkurā brīdī var uzņemt attēlus un ierakstīt videoklipus, izmantojot kameru."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Atļauja lietojumprogrammai vai pakalpojumam piekļūt sistēmas kamerām, lai uzņemtu attēlus un videoklipus"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Šī privileģētā vai sistēmas lietotne var jebkurā brīdī uzņemt attēlus un ierakstīt videoklipus, izmantojot sistēmas kameru. Lietotnei nepieciešama arī atļauja android.permission.CAMERA."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Atļaut lietojumprogrammai vai pakalpojumam saņemt atzvanus par kameras ierīču atvēršanu vai aizvēršanu"</string> @@ -1644,10 +1654,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vai palielināt skaļumu virs ieteicamā līmeņa?\n\nIlgstoši klausoties skaņu lielā skaļumā, var tikt bojāta dzirde."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vai izmantot pieejamības saīsni?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kad īsinājumtaustiņš ir ieslēgts, nospiežot abas skaļuma pogas un 3 sekundes turot tās, tiks aktivizēta pieejamības funkcija."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Vai ieslēgt pieejamības funkcijas?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vai ieslēgt pieejamības funkciju saīsni?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Turot nospiestus abus skaļuma taustiņus dažas sekundes, tiek ieslēgtas pieejamības funkcijas. Tas var mainīt ierīces darbību.\n\nPašreizējās funkcijas:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nAtlasītās funkcijas varat mainīt šeit: Iestatījumi > Pieejamība."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Vai ieslēgt pakalpojumu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vai ieslēgt <xliff:g id="SERVICE">%1$s</xliff:g> saīsni?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Turot nospiestus abus skaļuma taustiņus dažas sekundes, tiek ieslēgta pieejamības funkcija <xliff:g id="SERVICE">%1$s</xliff:g>. Tas var mainīt ierīces darbību.\n\nŠo saīsni uz citu funkciju varat mainīt šeit: Iestatījumi > Pieejamība."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ieslēgt"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Neieslēgt"</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index bf7220c7586e..b57307c51457 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"менува аудио поставки"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Овозможува апликацијата да ги менува глобалните аудио поставки, како што се јачината на звукот и кој звучник се користи за излез."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"снимај аудио"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Апликацијава може да снима аудио со микрофонот во секое време."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"испраќање наредби до SIM-картичката"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Овозможува апликацијата да испраќа наредби до SIM картичката. Ова е многу опасно."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"препознавајте ја физичката активност"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Апликацијава може да ја препознава вашата физичка активност."</string> <string name="permlab_camera" msgid="6320282492904119413">"снимај слики и видеа"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Апликацијава може да фотографира и да снима видеа со камерата во секое време."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Дозволете апликацијата или услугата да пристапува до системските камери за да фотографира и да снима видеа"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Оваа привилегирана или системска апликација може да фотографира и да снима видеа со системската камера во секое време. Потребно е апликацијата да ја има и дозволата android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Дозволете апликацијатa или услугата да прима повратни повици за отворањето или затворањето на уредите со камера."</string> @@ -988,7 +998,7 @@ <string name="searchview_description_search" msgid="1045552007537359343">"Пребарај"</string> <string name="searchview_description_query" msgid="7430242366971716338">"Пребарај барање"</string> <string name="searchview_description_clear" msgid="1989371719192982900">"Исчисти барање"</string> - <string name="searchview_description_submit" msgid="6771060386117334686">"Поднеси барање"</string> + <string name="searchview_description_submit" msgid="6771060386117334686">"Испрати барање"</string> <string name="searchview_description_voice" msgid="42360159504884679">"Гласовно пребарување"</string> <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"Овозможи „Истражувај со допир“?"</string> <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> сака да овозможи „Истражувај со допир“. Кога е вклучено „Истражувај со допир“, може да се слушнат или да се видат описи на она што е под вашиот прст или да се прават движења за комуницирање со таблетот."</string> @@ -1439,7 +1449,7 @@ <string name="upload_file" msgid="8651942222301634271">"Избери датотека"</string> <string name="no_file_chosen" msgid="4146295695162318057">"Не е избрана датотека"</string> <string name="reset" msgid="3865826612628171429">"Ресетирај"</string> - <string name="submit" msgid="862795280643405865">"Поднеси"</string> + <string name="submit" msgid="862795280643405865">"Испрати"</string> <string name="car_mode_disable_notification_title" msgid="8450693275833142896">"Апликацијата за возење работи"</string> <string name="car_mode_disable_notification_message" msgid="8954550232288567515">"Допрете за да излезете од апликацијата за возење."</string> <string name="back_button_label" msgid="4078224038025043387">"Назад"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Да го зголемиме звукот над препорачаното ниво?\n\nСлушањето звуци со голема јачина подолги периоди може да ви го оштети сетилото за слух."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Да се користи кратенка за „Пристапност“?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Кога е вклучена кратенката, ако ги притиснете двете копчиња за јачина на звук во времетраење од 3 секунди, ќе се стартува функција за пристапност."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Да се вклучат функциите за пристапност?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Да се вклучи кратенка за функциите за пристапност?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ако ги задржите притиснати двете копчиња за јачина на звук неколку секунди, ќе се вклучат функциите за пристапност. Ова може да го промени начинот на функционирање на уредот.\n\nТековни функции:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nМоже да ги промените избраните функции во „Поставки > Пристапност“."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Да се вклучи <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Да се вклучи кратенка за <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ако ги задржите притиснати двете копчиња за јачина на звук неколку секунди, ќе се вклучи функцијата за пристапност <xliff:g id="SERVICE">%1$s</xliff:g>. Ова може да го промени начинот на функционирање на уредот.\n\nМоже да ја измените кратенкава да биде за друга функција во „Поставки > Пристапност“."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Вклучи"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Не вклучувај"</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 513ed1d6c2e6..ce346c917f9a 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"നിങ്ങളുടെ ഓഡിയോ ക്രമീകരണങ്ങൾ മാറ്റുക"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"വോളിയവും ഔട്ട്പുട്ടിനായി ഉപയോഗിച്ച സ്പീക്കറും പോലുള്ള ആഗോള ഓഡിയോ ക്രമീകരണങ്ങൾ പരിഷ്ക്കരിക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ഓഡിയോ റെക്കോർഡ് ചെയ്യുക"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"ഈ ആപ്പിന് ഏത് സമയത്തും മൈക്രോഫോൺ ഉപയോഗിച്ചുകൊണ്ട് ഓഡിയോ റെക്കോർഡുചെയ്യാൻ കഴിയും."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM-ലേക്ക് കമാൻഡുകൾ അയയ്ക്കുക"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"സിമ്മിലേക്ക് കമാൻഡുകൾ അയയ്ക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. ഇത് വളരെ അപകടകരമാണ്."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"ശാരീരിക പ്രവർത്തനം തിരിച്ചറിയുക"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"നിങ്ങളുടെ ശാരീരിക പ്രവർത്തനം ഈ ആപ്പിന് തിരിച്ചറിയാനാവും."</string> <string name="permlab_camera" msgid="6320282492904119413">"ചിത്രങ്ങളും വീഡിയോകളും എടുക്കുക"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"ഏതുസമയത്തും ക്യാമറ ഉപയോഗിച്ചുകൊണ്ട് ചിത്രങ്ങൾ എടുക്കാനും വീഡിയോകൾ റെക്കോർഡുചെയ്യാനും ഈ ആപ്പിന് കഴിയും."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"ചിത്രങ്ങളും വീഡിയോകളും എടുക്കാൻ, സിസ്റ്റം ക്യാമറ ആക്സസ് ചെയ്യുന്നതിന് ആപ്പിനെയോ സേവനത്തെയോ അനുവദിക്കുക"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"സിസ്റ്റം ക്യാമറ ഉപയോഗിച്ച് ഏത് സമയത്തും ചിത്രങ്ങളെടുക്കാനും വീഡിയോകൾ റെക്കോർഡ് ചെയ്യാനും ഈ വിശേഷാധികാര അല്ലെങ്കിൽ സിസ്റ്റം ആപ്പിന് കഴിയും. ആപ്പിലും android.permission.CAMERA അനുമതി ഉണ്ടായിരിക്കണം"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ക്യാമറയുള്ള ഉപകരണങ്ങൾ ഓണാക്കുന്നതിനെയോ അടയ്ക്കുന്നതിനെയോ കുറിച്ചുള്ള കോൾബാക്കുകൾ സ്വീകരിക്കാൻ ആപ്പിനെയോ സേവനത്തെയോ അനുവദിക്കുക."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"മുകളിൽക്കൊടുത്തിരിക്കുന്ന ശുപാർശചെയ്ത ലെവലിലേക്ക് വോളിയം വർദ്ധിപ്പിക്കണോ?\n\nഉയർന്ന വോളിയത്തിൽ ദീർഘനേരം കേൾക്കുന്നത് നിങ്ങളുടെ ശ്രവണ ശേഷിയെ ദോഷകരമായി ബാധിക്കാം."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ഉപയോഗസഹായി കുറുക്കുവഴി ഉപയോഗിക്കണോ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"കുറുക്കുവഴി ഓണായിരിക്കുമ്പോൾ, രണ്ട് വോളിയം ബട്ടണുകളും 3 സെക്കൻഡ് നേരത്തേക്ക് അമർത്തുന്നത് ഉപയോഗസഹായി ഫീച്ചർ ആരംഭിക്കും."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ഉപയോഗസഹായി ഫീച്ചറുകൾ ഓണാക്കണോ?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ഉപയോഗസഹായി ഫീച്ചറുകൾക്കുള്ള കുറുക്കുവഴി ഓണാക്കണോ?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"രണ്ട് വോളിയം കീകളും അൽപ്പ നേരത്തേക്ക് അമർത്തിപ്പിടിക്കുന്നത്, ഉപയോഗസഹായി ഫീച്ചറുകൾ ഓണാക്കുന്നു. നിങ്ങളുടെ ഉപകരണം പ്രവർത്തിക്കുന്ന രീതിയെ ഇത് മാറ്റിയേക്കാം.\n\nനിലവിലുള്ള ഫീച്ചറുകൾ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nതിരഞ്ഞെടുത്ത ഫീച്ചറുകൾ ക്രമീകരണം > ഉപയോഗസഹായി എന്നതിൽ മാറ്റാനാവും."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ഓണാക്കണോ?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> കുറുക്കുവഴി ഓണാക്കണോ?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"രണ്ട് വോളിയം കീകളും അൽപ്പ നേരത്തേക്ക് അമർത്തിപ്പിടിക്കുന്നത് ഉപയോഗസഹായി ഫീച്ചറായ <xliff:g id="SERVICE">%1$s</xliff:g> എന്നതിനെ ഓണാക്കുന്നു. നിങ്ങളുടെ ഉപകരണം പ്രവർത്തിക്കുന്ന വിധം ഇത് മാറ്റിയേക്കാം.\n\nക്രമീകരണം > ഉപയോഗസഹായി എന്നതിലെ മറ്റൊരു ഫീച്ചറിലേക്ക് നിങ്ങൾക്ക് ഈ കുറുക്കുവഴി മാറ്റാനാവും."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"ഓണാക്കുക"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"ഓണാക്കരുത്"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index b95b6897eb7a..313c4066cc9f 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"Аудио тохиргоо солих"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Апп нь дууны хэмжээ, спикерын гаралтад ашиглагдах глобал аудио тохиргоог өөрчлөх боломжтой."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"аудио бичих"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Энэ апп ямар ч үед микрофон ашиглан аудио бичих боломжтой."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM картад тушаал илгээх"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Апп-д SIM рүү комманд илгээхийг зөвшөөрнө. Энэ маш аюултай."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"биеийн дасгал хөдөлгөөн таних"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Энэ апп таны биеийн дасгал хөдөлгөөнийг таних боломжтой."</string> <string name="permlab_camera" msgid="6320282492904119413">"зураг авах болон видео бичих"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Энэ апп ямар ч үед камер ашиглан зураг авж, видео хийх боломжтой."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Видео болон зураг авахын тулд апп эсвэл үйлчилгээнд хандахыг системийн камерт зөвшөөрөх"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Энэ хамгаалагдсан эсвэл системийн апп нь системийн камер ашиглан ямар ч үед зураг авч, видео бичих боломжтой. Мөн түүнчлэн, апп нь android.permission.CAMERA-н зөвшөөрөлтэй байх шаардлагатай"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Аппликэйшн эсвэл үйлчилгээнд камерын төхөөрөмжүүдийг нээж эсвэл хааж байгаа тухай залгасан дуудлага хүлээн авахыг зөвшөөрөх."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Дууг санал болгосноос чанга болгож өсгөх үү?\n\nУрт хугацаанд чанга хөгжим сонсох нь таны сонсголыг муутгаж болно."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Хүртээмжийн товчлолыг ашиглах уу?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Товчлол асаалттай үед дууны түвшний хоёр товчлуурыг хамтад нь 3 секунд дарснаар хандалтын онцлогийг эхлүүлнэ."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Хандалтын онцлогуудыг асаах уу?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Хандалтын онцлогуудын товчлолыг асаах уу?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Дууны түвшний түлхүүрийг хэдэн секундийн турш зэрэг дарснаар хандалтын онцлогууд асна. Энэ нь таны төхөөрөмжийн ажиллах зарчмыг өөрчилж болзошгүй.\n\nОдоогийн онцлогууд:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТа сонгосон онцлогуудыг Тохиргоо > Хандалт хэсэгт өөрчлөх боломжтой."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g>-г асаах уу?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g>-н товчлолыг асаах уу?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Дууны түвшний түлхүүрийг хэдэн секундийн турш зэрэг дарах нь хандалтын онцлог болох <xliff:g id="SERVICE">%1$s</xliff:g>-г асаадаг. Энэ нь таны төхөөрөмжийн ажиллах зарчмыг өөрчилж болзошгүй.\n\nТа Тохиргоо > Хандалт хэсэгт энэ товчлолыг өөр онцлогт оноож өөрчлөх боломжтой."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Асаах"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Бүү асаа"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 40c7dbd384c9..effa6b6e0b35 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"आपल्या ऑडिओ सेटिंग्ज बदला"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"व्हॉल्यूम आणि आउटपुटसाठी कोणता स्पीकर वापरला आहे यासारख्या समग्र ऑडिओ सेटिंग्ज सुधारित करण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ऑडिओ रेकॉर्ड"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"हा अॅप कोणत्याही वेळी मायक्रोफोन वापरून ऑडिओ रेकॉर्ड करू शकता."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"सिम वर कमांड पाठवा"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"अॅप ला सिम वर कमांड पाठविण्याची अनुमती देते. हे खूप धोकादायक असते."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"शारीरिक ॲक्टिव्हिटी ओळखा"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"हे अॅप तुमच्या शारीरिक ॲक्टिव्हिटी ओळखू शकते."</string> <string name="permlab_camera" msgid="6320282492904119413">"चित्रे आणि व्हिडिओ घ्या"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"हा अॅप कोणत्याही वेळी कॅमेरा वापरून चित्रेे घेऊ आणि व्हिडिओ रेकॉर्ड करू शकतो."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"फोटो आणि व्हिडिओ काढण्यासाठी ॲप्लिकेशन किंवा सेवेला सिस्टम कॅमेरे ॲक्सेस करण्याची अनुमती द्या"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"हे विशेषाधिकृत किंवा सिस्टम ॲप कधीही सिस्टम कॅमेरा वापरून फोटो आणि व्हिडिओ रेकॉर्ड करू शकते. ॲपकडे android.permission.CAMERA परवानगी असण्याचीदेखील आवश्यकता आहे"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"एखाद्या अॅप्लिकेशन किंवा सेवेला कॅमेरा डिव्हाइस सुरू किंवा बंद केल्याची कॉलबॅक मिळवण्याची अनुमती द्या."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"शिफारस केलेल्या पातळीच्या वर आवाज वाढवायचा?\n\nउच्च आवाजात दीर्घ काळ ऐकण्याने आपल्या श्रवणशक्तीची हानी होऊ शकते."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"प्रवेशयोग्यता शॉर्टकट वापरायचा?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"शॉर्टकट सुरू असताना, दोन्ही व्हॉल्यूम बटणे तीन सेकंदांसाठी दाबून ठेवल्याने अॅक्सेसिबिलिटी वैशिष्ट्य सुरू होईल."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"अॅक्सेसिबिलिटी वैशिष्ट्ये सुरू करायची आहेत का?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"अॅक्सेसिबिलिटी वैशिष्ट्यांसाठी शॉर्टकट सुरू करायचा आहे का?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"दोन्ही व्हॉल्यूम की काही सेकंद धरून ठेवल्याने अॅक्सेसिबिलिटी वैशिष्ट्ये सुरू होतात. यामुळे तुमचे डिव्हाइस कसे काम करते हे पूर्णपणे बदलते.\n\nसध्याची वैशिष्ट्ये:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nतुम्ही हा शॉर्टकट सेटिंग्ज > अॅक्सेसिबिलिटी मध्ये बदलू शकता."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> सुरू करायची आहे का?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> शॉर्टकट सुरू करायचा आहे का?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"दोन्ही व्हॉल्यूम की काही सेकंद धरून ठेवल्याने <xliff:g id="SERVICE">%1$s</xliff:g>, एक अॅक्सेसिबिलिटी वैशिष्ट्य सुरू होते. यामुळे तुमचे डिव्हाइस कसे काम करते हे पूर्णपणे बदलते.\n\nतुम्ही हा शॉर्टकट सेटिंग्ज > अॅक्सेसिबिलिटी मध्ये बदलू शकता."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"सुरू करा"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"सुरू करू नका"</string> @@ -1829,8 +1839,7 @@ <item quantity="other">%d तासासाठी</item> <item quantity="one">1 तासासाठी</item> </plurals> - <!-- no translation found for zen_mode_until_next_day (1403042784161725038) --> - <skip /> + <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>पर्यंत"</string> <string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> पर्यंत"</string> <string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> पर्यंत (पुढील अलार्म)"</string> <string name="zen_mode_forever" msgid="740585666364912448">"तुम्ही बंद करेपर्यंत"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index fb24a06dd3a5..5849c3698487 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"tukar tetapan audio anda"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Membenarkan apl untuk mengubah suai tetapan audio global seperti kelantangan dan pembesar suara mana digunakan untuk output."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"rakam audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Apl ini boleh merakam audio menggunakan mikrofon pada bila-bila masa."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"hantar perintah ke SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Membenarkan apl menghantar arahan kepada SIM. Ini amat berbahaya."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"camkan aktiviti fizikal"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Apl ini dapat mengecam aktiviti fizikal anda."</string> <string name="permlab_camera" msgid="6320282492904119413">"ambil gambar dan video"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Apl ini boleh mengambil gambar dan merakam video menggunakan kamera pada bila-bila masa."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Benarkan aplikasi atau perkhidmatan mengakses kamera sistem untuk mengambil gambar dan video"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Apl terlindung atau apl sistem ini boleh mengambil gambar dan merakam video menggunakan kamera sistem pada bila-bila masa. Apl juga perlu mempunyai kebenaran android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Benarkan aplikasi atau perkhidmatan menerima panggilan balik tentang peranti kamera yang dibuka atau ditutup."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Naikkan kelantangan melebihi paras yang disyokorkan?\n\nMendengar pada kelantangan yang tinggi untuk tempoh yang lama boleh merosakkan pendengaran anda."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gunakan Pintasan Kebolehaksesan?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Apabila pintasan dihidupkan, tindakan menekan kedua-dua butang kelantangan selama 3 saat akan memulakan ciri kebolehaksesan."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Hidupkan ciri kebolehaksesan?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Hidupkan pintasan untuk ciri kebolehaksesan?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Tindakan menahan kedua-dua kekunci kelantangan selama beberapa saat akan menghidupkan ciri kebolehaksesan. Hal ini mungkin mengubah cara peranti anda berfungsi.\n\nCiri semasa:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nAnda boleh menukar ciri yang dipilih dalam Tetapan > Kebolehaksesan."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Hidupkan <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Hidupkan pintasan <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Tindakan menahan kedua-dua kekunci kelantangan selama beberapa saat akan menghidupkan <xliff:g id="SERVICE">%1$s</xliff:g>, iaitu satu ciri kebolehaksesan. Ini mungkin mengubah cara peranti anda berfungsi.\n\nAnda boleh menukar pintasan ini kepada ciri lain dalam Tetapan > Kebolehaksesan."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Hidupkan"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Jangan hidupkan"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 2c3fbcbeac41..be8820824706 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"သင့်အသံအပြင်အဆင်အားပြောင်းခြင်း"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"အပလီကေးရှင်းအား အသံအတိုးအကျယ်နှင့် အထွက်ကို မည်သည့်စပီကာကို သုံးရန်စသည်ဖြင့် စက်တစ်ခုလုံးနှင့်ဆိုင်သော အသံဆိုင်ရာ ဆက်တင်များ ပြင်ဆင်ခွင့် ပြုရန်"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"အသံဖမ်းခြင်း"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"ဤအက်ပ်သည် မိုက်ခရိုဖုန်းကို အသုံးပြုပြီး အချိန်မရွေး အသံသွင်းနိုင်ပါသည်။"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM ထံသို့ ညွှန်ကြားချက်များကို ပို့ပါ"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"အက်ပ်အား ဆင်းမ်ကဒ်ဆီသို့ အမိန့်များ ပေးပို့ခွင့် ပြုခြင်း။ ဤခွင့်ပြုမှုမှာ အန္တရာယ်အလွန် ရှိပါသည်။"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"ကိုယ်လက်လှုပ်ရှားမှုကို မှတ်သားပါ"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"ဤအက်ပ်က သင်၏ကိုယ်လက်လှုပ်ရှားမှုကို မှတ်သားနိုင်ပါသည်။"</string> <string name="permlab_camera" msgid="6320282492904119413">"ဓါတ်ပုံနှင့်ဗွီဒီယိုရိုက်ခြင်း"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"ဤအက်ပ်သည် ကင်မရာကို အသုံးပြု၍ ဓာတ်ပုံနှင့် ဗီဒီယိုများကို အချိန်မရွေး ရိုက်ကူးနိုင်ပါသည်။"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"ဓာတ်ပုံနှင့် ဗီဒီယိုများရိုက်ရန်အတွက် စနစ်ကင်မရာများကို အက်ပ် သို့မဟုတ် ဝန်ဆောင်မှုအား အသုံးပြုခွင့်ပေးခြင်း"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"ဤခွင့်ပြုထားသည့် သို့မဟုတ် စနစ်အက်ပ်က စနစ်ကင်မရာအသုံးပြုပြီး ဓာတ်ပုံနှင့် ဗီဒီယိုများကို အချိန်မရွေး ရိုက်ကူးနိုင်သည်။ အက်ပ်ကလည်း android.permission.CAMERA ခွင့်ပြုချက် ရှိရပါမည်"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ကင်မရာစက်များ ပွင့်နေခြင်း သို့မဟုတ် ပိတ်နေခြင်းနှင့် ပတ်သက်ပြီး ပြန်လည်ခေါ်ဆိုမှုများ ရယူရန် အပလီကေးရှင်း သို့မဟုတ် ဝန်ဆောင်မှုကို ခွင့်ပြုခြင်း။"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"အသံကို အကြံပြုထားသည့် ပမာဏထက် မြှင့်ပေးရမလား?\n\nအသံကို မြင့်သည့် အဆင့်မှာ ကြာရှည်စွာ နားထောင်ခြင်းက သင်၏ နားကို ထိခိုက်စေနိုင်သည်။"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"အများသုံးစွဲနိုင်မှု ဖြတ်လမ်းလင့်ခ်ကို အသုံးပြုလိုပါသလား။"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ဖြတ်လမ်းလင့်ခ်ကို ဖွင့်ထားစဉ် အသံထိန်းခလုတ် နှစ်ခုစလုံးကို ၃ စက္ကန့်ခန့် ဖိထားခြင်းဖြင့် အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုကို ဖွင့်နိုင်သည်။"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများ ဖွင့်မလား။"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများအတွက် ဖြတ်လမ်းကို ဖွင့်မလား။"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"အသံခလုတ်နှစ်ခုလုံးကို စက္ကန့်အနည်းငယ် ဖိထားခြင်းက အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများ ဖွင့်ပေးသည်။ ဤလုပ်ဆောင်ချက်က သင့်စက်အလုပ်လုပ်ပုံကို ပြောင်းလဲနိုင်သည်။\n\nလက်ရှိ ဝန်ဆောင်မှုများ-\n<xliff:g id="SERVICE">%1$s</xliff:g>\n\'ဆက်တင်များ > အများသုံးစွဲနိုင်မှု\' တွင် ရွေးထားသည့် ဝန်ဆောင်မှုများကို ပြောင်းနိုင်သည်။"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ဖွင့်မလား။"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> ဖြတ်လမ်းကို ဖွင့်မလား။"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"အသံခလုတ်နှစ်ခုလုံးကို စက္ကန့်အနည်းငယ် ဖိထားခြင်းက အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုဖြစ်သော <xliff:g id="SERVICE">%1$s</xliff:g> ကို ဖွင့်ပေးသည်။ ဤလုပ်ဆောင်ချက်က သင့်စက်အလုပ်လုပ်ပုံကို ပြောင်းလဲနိုင်သည်။\n\nဤဖြတ်လမ်းလင့်ခ်ကို \'ဆက်တင်များ > အများသုံးစွဲနိုင်မှု\' တွင် နောက်ဝန်ဆောင်မှုတစ်ခုသို့ ပြောင်းနိုင်သည်။"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"ဖွင့်ရန်"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"မဖွင့်ပါနှင့်"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index fced2070c93b..a18df1682e22 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"endre lydinnstillinger"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Lar appen endre globale lydinnstillinger slik som volum og hvilken høyttaler som brukes for lydavspilling."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ta opp lyd"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Denne appen kan når som helst ta opp lyd med mikrofonen."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"sende kommandoer til SIM-kortet"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Lar appen sende kommandoer til SIM-kortet. Dette er veldig farlig."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"gjenkjenn fysisk aktivitet"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Denne appen kan gjenkjenne den fysiske aktiviteten din."</string> <string name="permlab_camera" msgid="6320282492904119413">"ta bilder og videoer"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Denne appen kan når som helst ta bilder og spille inn videoer ved hjelp av kameraet."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Gi en app eller tjeneste tilgang til systemkameraene for å ta bilder og spille inn videoer"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Denne privilegerte appen eller systemappen kan når som helst ta bilder og spille inn videoer med et systemkamera. Dette krever at appen også har tillatelsen android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Tillat at en app eller tjeneste mottar tilbakekallinger om kameraenheter som åpnes eller lukkes."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vil du øke volumet til over anbefalt nivå?\n\nHvis du hører på et høyt volum over lengre perioder, kan det skade hørselen din."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vil du bruke tilgjengelighetssnarveien?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Når snarveien er på, starter en tilgjengelighetsfunksjon når du trykker inn begge volumknappene i tre sekunder."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Vil du slå på tilgjengelighetsfunksjoner?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vil du slå på snarveien for tilgjengelighetsfunksjoner?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hvis du holder inne volumtastene i noen sekunder, slås tilgjengelighetsfunksjoner på. Dette kan endre hvordan enheten din fungerer.\n\nNåværende funksjoner:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kan endre valgte funksjoner i Innstillinger > Tilgjengelighet."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Vil du slå på <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vil du slå på <xliff:g id="SERVICE">%1$s</xliff:g>-snarveien?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Hvis du holder inne begge volumtastene i noen sekunder, slår du på <xliff:g id="SERVICE">%1$s</xliff:g>, en tilgjengelighetsfunksjon. Dette kan endre hvordan enheten din fungerer.\n\nDu kan endre denne snarveien til en annen funksjon i Innstillinger > Tilgjengelighet."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Slå på"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ikke slå på"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index fadabe83dd55..b327342ab0d5 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"तपाईँका अडियो सेटिङहरू परिवर्तन गर्नुहोस्"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"एपलाई ग्लोबल अडियो सेटिङहरू परिमार्जन गर्न अनुमति दिन्छ, जस्तै भोल्युम र आउटपुटको लागि कुन स्पिकर प्रयोग गर्ने।"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"अडियो रेकर्ड गर्नुहोस्"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"यस अनुप्रयोगले जुनसुकै समय माइक्रोफोनको प्रयोग गरी अडियो रेकर्ड गर्न सक्छ।"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM मा आदेशहरू पठाउन दिनुहोस्"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM लाई आदेश पठाउन एपलाई अनुमति दिन्छ। यो निकै खतरनाक हुन्छ।"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"शारीरिक गतिविधि पहिचान गर्नुहोस्"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"यो अनुप्रयोगले तपाईंको शारीरिक गतिविधिको पहिचान गर्न सक्छ।"</string> <string name="permlab_camera" msgid="6320282492904119413">"फोटोहरू र भिडियोहरू लिनुहोस्।"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"यस अनुप्रयोगले जुनसुकै समय क्यामेराको प्रयोग गरी फोटो खिच्न र भिडियो रेकर्ड गर्न सक्छ।"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"एप वा सेवालाई फोटो र भिडियो खिच्न प्रणालीका क्यामेराहरूमाथि पहुँच राख्न दिनुहोस्"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"प्रणालीको यस विशेषाधिकार प्राप्त अनुप्रयोगले जुनसुकै बेला प्रणालीको क्यामेरा प्रयोग गरी फोटो खिच्न र भिडियो रेकर्ड गर्न सक्छ। अनुप्रयोगसँग पनि android.permission.CAMERA प्रयोग गर्ने अनुमति हुनु पर्छ"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"कुनै एप वा सेवालाई खोलिँदै वा बन्द गरिँदै गरेका क्यामेरा यन्त्रहरूका बारेमा कलब्याक प्राप्त गर्ने अनुमति दिनुहोस्।"</string> @@ -1622,10 +1632,12 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"सिफारिस तहभन्दा आवाज ठुलो गर्नुहुन्छ?\n\nलामो समय सम्म उच्च आवाजमा सुन्दा तपाईँको सुन्ने शक्तिलाई हानी गर्न सक्छ।"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"पहुँच सम्बन्धी सर्टकट प्रयोग गर्ने हो?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"यो सर्टकट सक्रिय हुँदा, ३ सेकेन्डसम्म दुवै भोल्युम बटन थिच्नुले पहुँचसम्बन्धी कुनै सुविधा सुरु गर्ने छ।"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"पहुँचसम्बन्धी सुविधाहरू सक्रिय गर्ने हो?"</string> + <!-- no translation found for accessibility_shortcut_multiple_service_warning_title (3135860819356676426) --> + <skip /> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"केही सेकेन्डसम्म दुवै भोल्युम बटन थिचिराख्नुभयो भने पहुँचसम्बन्धी सुविधाहरू सक्रिय हुन्छ। यसले तपाईंको यन्त्रले काम गर्ने तरिका परिवर्तन गर्न सक्छ।\n\nहालका सुविधाहरू:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nतपाईं सेटिङ > पहुँचमा गएर चयन गरिएका सुविधाहरू परिवर्तन गर्न सक्नुहुन्छ।"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> सक्रिय गर्ने हो?"</string> + <!-- no translation found for accessibility_shortcut_single_service_warning_title (1909518473488345266) --> + <skip /> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"केही सेकेन्डसम्म दुवै भोल्युम बटन थिचिराख्नुले <xliff:g id="SERVICE">%1$s</xliff:g> नामक पहुँचसम्बन्धी सुविधा सक्रिय गर्छ। यसले तपाईंको यन्त्रले काम गर्ने तरिका परिवर्तन गर्न सक्छ।\n\nतपाईं सेटिङ > पहुँचमा गई यो सर्टकटमार्फत अर्को सुविधा खुल्ने बनाउन सक्नुहुन्छ।"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"सक्रिय गरियोस्"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"सक्रिय नगरियोस्"</string> @@ -1829,8 +1841,7 @@ <item quantity="other">%d घन्टाका लागि</item> <item quantity="one">१ घन्टाको लागि</item> </plurals> - <!-- no translation found for zen_mode_until_next_day (1403042784161725038) --> - <skip /> + <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> सम्म"</string> <string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> सम्म"</string> <string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (अर्को अलार्म) सम्म"</string> <string name="zen_mode_forever" msgid="740585666364912448">"तपाईंले निष्क्रिय नपार्नुभएसम्म"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 337bf17fd7fa..d601905d8f10 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"je audio-instellingen wijzigen"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Hiermee kan de app algemene audio-instellingen wijzigen zoals het volume en welke luidspreker wordt gebruikt voor de uitvoer."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"audio opnemen"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Deze app kan op elk moment audio opnemen met de microfoon."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"opdrachten verzenden naar de simkaart"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Hiermee kan de app opdrachten verzenden naar de simkaart. Dit is erg gevaarlijk."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"fysieke activiteit herkennen"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Deze app kan je fysieke activiteit herkennen."</string> <string name="permlab_camera" msgid="6320282492904119413">"foto\'s en video\'s maken"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Deze app kan op elk moment foto\'s maken en video\'s opnemen met de camera."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Een app of service toegang tot systeemcamera\'s geven om foto\'s en video\'s te maken"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Deze gemachtigde app of systeem-app kan op elk gewenst moment foto\'s maken en video\'s opnemen met een systeemcamera. De app moet ook het recht android.permission.CAMERA hebben."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Een app of service toestaan callbacks te ontvangen over camera-apparaten die worden geopend of gesloten."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Volume verhogen tot boven het aanbevolen niveau?\n\nAls je langere tijd op hoog volume naar muziek luistert, raakt je gehoor mogelijk beschadigd."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Snelkoppeling toegankelijkheid gebruiken?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Als de snelkoppeling is ingeschakeld, kun je drie seconden op beide volumeknoppen drukken om een toegankelijkheidsfunctie te starten."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Toegankelijkheidsfuncties inschakelen?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Snelkoppeling voor toegankelijkheidsfuncties inschakelen?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Als je beide volumetoetsen een paar seconden ingedrukt houdt, schakel je de toegankelijkheidsfuncties in. Hierdoor kan de manier veranderen waarop je apparaat werkt.\n\nHuidige functies:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nJe kunt de geselecteerde functies wijzigen via Instellingen > Toegankelijkheid."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> inschakelen?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Snelkoppeling voor <xliff:g id="SERVICE">%1$s</xliff:g> inschakelen?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Als je beide volumetoetsen een paar seconden ingedrukt houdt, wordt de toegankelijkheidsfunctie <xliff:g id="SERVICE">%1$s</xliff:g> ingeschakeld. Hierdoor kan de manier veranderen waarop je apparaat werkt.\n\nJe kunt deze sneltoets op een andere functie instellen via Instellingen > Toegankelijkheid."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Inschakelen"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Niet inschakelen"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 85ca06564d7a..f3a225a6c629 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ଆପଣଙ୍କ ଅଡିଓ ସେଟିଙ୍ଗକୁ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ଆପ୍କୁ ଗ୍ଲୋବାଲ୍ ଅଡିଓ ସେଟିଙ୍ଗ, ଯେପରିକି ଭଲ୍ୟୁମ୍କୁ ସଂଶୋଧିତ କରିବାକୁ ଏବଂ ଆଉଟପୁଟ୍ ପାଇଁ ସ୍ପିକର୍ ବ୍ୟବହାର କରିବାକୁ ଅନୁମତି ଦେଇଥାଏ।"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ଅଡିଓ ରେକର୍ଡ କରନ୍ତୁ"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"ଏହି ଆପ୍ ଯେକୌଣସି ସମୟରେ ମାଇକ୍ରୋଫୋନ୍ ବ୍ୟବହାର କରି ଅଡିଓ ରେକର୍ଡ କରିପାରିବ।"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIMକୁ କମାଣ୍ଡ ପଠାନ୍ତୁ"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIMକୁ କମାଣ୍ଡ ପଠାଇବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ। ଏହା ବହୁତ ବିପଦପୂର୍ଣ୍ଣ।"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"ଶାରୀରିକ ଗତିବିଧି ଚିହ୍ନଟକରେ"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"ଏହି ଆପ୍ଣ ଆପଣଙ୍କ ଶାରୀରିକ ଗତିବିଧିକୁ ଚିହ୍ନଟ କରିପାରେ"</string> <string name="permlab_camera" msgid="6320282492904119413">"ଫଟୋ ଓ ଭିଡିଓଗୁଡ଼ିକୁ ନିଅନ୍ତୁ"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"ଏହି ଆପ୍ ଯେକୌଣସି ସମୟରେ କ୍ୟାମେରା ବ୍ୟବହାର କରି ଫଟୋ ଉଠାଇପାରେ ଏବଂ ଭିଡିଓ ରେକର୍ଡ କରିପାରେ।"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"ଛବି ଏବଂ ଭିଡିଓଗୁଡ଼ିକୁ ନେବା ପାଇଁ ସିଷ୍ଟମ୍ କ୍ୟାମେରାଗୁଡ଼ିକୁ କୌଣସି ଆପ୍ଲିକେସନ୍ କିମ୍ବା ସେବା ଆକ୍ସେସ୍ ଅନୁମତି ଦିଅନ୍ତୁ"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"ବିଶେଷ ଅଧିକାର ଥିବା ଏହି ଆପ୍ କିମ୍ବା ସିଷ୍ଟମ୍ ଆପ୍ ଯେ କୌଣସି ସମୟରେ ଏକ ସିଷ୍ଟମ୍ କ୍ୟାମେରା ବ୍ୟବହାର କରି ଛବି ଉଠାଇପାରିବ ଏବଂ ଭିଡିଓ ରେକର୍ଡ କରିପାରିବ। ଆପରେ ମଧ୍ୟ android.permission.CAMERA ଅନୁମତି ଆବଶ୍ୟକ"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"କ୍ୟାମେରା ଡିଭାଇସଗୁଡ଼ିକ ଖୋଲିବା କିମ୍ବା ବନ୍ଦ କରିବା ବିଷୟରେ କଲବ୍ୟାକଗୁଡ଼ିକ ପାଇବାକୁ ଏକ ଆପ୍ଲିକେସନ୍ କିମ୍ବା ସେବାକୁ ଅନୁମତି ଦିଅନ୍ତୁ।"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ମାତ୍ରା ବଢ଼ାଇ ସୁପାରିଶ ସ୍ତର ବଢ଼ାଉଛନ୍ତି? \n\n ଲମ୍ବା ସମୟ ପର୍ଯ୍ୟନ୍ତ ଉଚ୍ଚ ଶବ୍ଦରେ ଶୁଣିଲେ ଆପଣଙ୍କ ଶ୍ରବଣ ଶକ୍ତି ଖରାପ ହୋଇପାରେ।"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ଆକ୍ସେସବିଲିଟି ଶର୍ଟକଟ୍ ବ୍ୟବହାର କରିବେ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ସର୍ଟକଟ୍ ଚାଲୁ ଥିବା ବେଳେ, ଉଭୟ ଭଲ୍ୟୁମ୍ ବଟନ୍ 3 ସେକେଣ୍ଡ ପାଇଁ ଦବାଇବା ଦ୍ୱାରା ଏକ ଆକ୍ସେସବିଲିଟି ଫିଚର୍ ଆରମ୍ଭ ହେବ।"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ଆକ୍ସେସିବିଲିଟୀ ଫିଚରଗୁଡ଼ିକୁ ଚାଲୁ କରିବେ?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ଆକ୍ସେସିବିଲିଟୀ ଫିଚରଗୁଡ଼ିକ ପାଇଁ ସର୍ଟକଟ୍ ଚାଲୁ କରିବେ?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"କିଛି ସେକେଣ୍ଡ ପାଇଁ ଉଭୟ ଭଲ୍ୟୁମ୍ କୀ’କୁ ଧରି ରଖିବା ଫଳରେ ଆକ୍ସେସିବିଲିଟୀ ଫିଚରଗୁଡ଼ିକ ଚାଲୁ ହୁଏ। ଏହା ଆପଣଙ୍କ ଡିଭାଇସ୍ କିପରି କାମ କରେ ତାହା ପରିବର୍ତ୍ତନ କରିପାରେ।\n\nବର୍ତ୍ତମାନର ଫିଚରଗୁଡ଼ିକ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n ଆପଣ ସେଟିଂସ୍ &gt ଆକ୍ସେସିବିଲିଟୀରେ ଚୟନିତ ଫିଚରଗୁଡ଼ିକୁ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ଚାଲୁ କରିବେ?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> ସର୍ଟକଟ୍ ଚାଲୁ କରିବେ?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"କିଛି ସେକେଣ୍ଡ ପାଇଁ ଉଭୟ ଭଲ୍ୟୁମ୍ କୀ’କୁ ଧରି ରଖିବା ଫଳରେ ଏକ ଆକ୍ସେସିବିଲିଟୀ ଫିଚର୍ <xliff:g id="SERVICE">%1$s</xliff:g> ଚାଲୁ ହୁଏ। ଏହା ଆପଣଙ୍କ ଡିଭାଇସ୍ କିପରି କାମ କରେ ତାହା ପରିବର୍ତ୍ତନ କରିପାରେ।\n\nଆପଣ ସେଟିଂସ୍ &gt ଆକ୍ସେସିବିଲିଟୀରେ ଏହି ସର୍ଚକଟକୁ ଅନ୍ୟ ଏକ ଫିଚରରେ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"ଚାଲୁ କରନ୍ତୁ"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"ଚାଲୁ କରନ୍ତୁ ନାହିଁ"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index ed663a8bb5d6..6ea5f5227386 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ਆਪਣੀਆਂ ਆਡੀਓ ਸੈਟਿੰਗਾਂ ਬਦਲੋ"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ਐਪ ਨੂੰ ਗਲੋਬਲ ਆਡੀਓ ਸੈਟਿੰਗਾਂ ਸੰਸ਼ੋਧਿਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ ਜਿਵੇਂ ਅਵਾਜ਼ ਅਤੇ ਆਊਟਪੁਟ ਲਈ ਕਿਹੜਾ ਸਪੀਕਰ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ।"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">" ਆਡੀਓ ਰਿਕਾਰਡ ਕਰਨ"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"ਇਹ ਐਪ ਕਿਸੇ ਵੀ ਸਮੇਂ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਆਡੀਓ ਫ਼ਾਈਲ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ।"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM ਨੂੰ ਕਮਾਂਡਾਂ ਭੇਜੋ"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"ਐਪ ਨੂੰ SIM ਨੂੰ ਕਮਾਂਡਾਂ ਭੇਜਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਬਹੁਤ ਘਾਤਕ ਹੈ।"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"ਸਰੀਰਕ ਸਰਗਰਮੀ ਨੂੰ ਪਛਾਣਨਾ"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"ਇਹ ਐਪ ਤੁਹਾਡੀ ਸਰੀਰਕ ਸਰਗਰਮੀ ਨੂੰ ਪਛਾਣ ਸਕਦੀ ਹੈ।"</string> <string name="permlab_camera" msgid="6320282492904119413">"ਤਸਵੀਰਾਂ ਅਤੇ ਵੀਡੀਓ ਬਣਾਓ"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"ਇਹ ਐਪ ਕਿਸੇ ਵੀ ਸਮੇਂ ਕੈਮਰੇ ਨੂੰ ਵਰਤ ਕੇ ਤਸਵੀਰਾਂ ਖਿੱਚ ਸਕਦੀ ਹੈ ਅਤੇ ਵੀਡੀਓ ਫ਼ਾਈਲਾਂ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ।"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"ਸਿਸਟਮ ਕੈਮਰੇ ਨੂੰ ਤਸਵੀਰਾਂ ਅਤੇ ਵੀਡੀਓ ਬਣਾਉਣ ਲਈ ਐਪਲੀਕੇਸ਼ਨ ਜਾਂ ਸੇਵਾ ਤੱਕ ਪਹੁੰਚ ਦਿਓ"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"ਇਹ ਵਿਸ਼ੇਸ਼ ਅਧਿਕ੍ਰਿਤ ਜਾਂ ਸਿਸਟਮ ਐਪ ਕਿਸੇ ਵੇਲੇ ਵੀ ਸਿਸਟਮ ਕੈਮਰੇ ਨੂੰ ਵਰਤ ਕੇ ਤਸਵੀਰਾਂ ਖਿੱਚ ਸਕਦੀ ਹੈ ਅਤੇ ਵੀਡੀਓ ਫ਼ਾਈਲਾਂ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ। ਐਪ ਨੂੰ ਵੀ android.permission.CAMERA ਇਜਾਜ਼ਤ ਦੀ ਲੋੜ ਹੈ"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ਐਪਲੀਕੇਸ਼ਨ ਜਾਂ ਸੇਵਾ ਨੂੰ ਕੈਮਰਾ ਡੀਵਾਈਸਾਂ ਦੇ ਚਾਲੂ ਜਾਂ ਬੰਦ ਕੀਤੇ ਜਾਣ ਬਾਰੇ ਕਾਲਬੈਕ ਪ੍ਰਾਪਤ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ।"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ਕੀ ਵੌਲਿਊਮ ਸਿਫ਼ਾਰਸ਼ ਕੀਤੇ ਪੱਧਰ ਤੋਂ ਵਧਾਉਣੀ ਹੈ?\n\nਲੰਮੇ ਸਮੇਂ ਤੱਕ ਉੱਚ ਵੌਲਿਊਮ ਤੇ ਸੁਣਨ ਨਾਲ ਤੁਹਾਡੀ ਸੁਣਨ ਸ਼ਕਤੀ ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚ ਸਕਦਾ ਹੈ।"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ਕੀ ਪਹੁੰਚਯੋਗਤਾ ਸ਼ਾਰਟਕੱਟ ਵਰਤਣਾ ਹੈ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਹੋਣ \'ਤੇ, ਕਿਸੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਦੋਵੇਂ ਅਵਾਜ਼ ਬਟਨਾਂ ਨੂੰ 3 ਸਕਿੰਟ ਲਈ ਦਬਾ ਕੇ ਰੱਖੋ।"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ਕੀ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ਕੀ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਲਈ ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ਕੁਝ ਸਕਿੰਟਾਂ ਲਈ ਦੋਵੇਂ ਅਵਾਜ਼ੀ ਕੁੰਜੀਆਂ ਨੂੰ ਦਬਾਈ ਰੱਖਣਾ, ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਚਾਲੂ ਕਰ ਦਿੰਦਾ ਹੈ। ਇਹ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੇ ਕੰਮ ਕਰਨ ਦੇ ਤਰੀਕੇ ਨੂੰ ਬਦਲ ਸਕਦਾ ਹੈ।\n\nਮੌਜੂਦਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nਸੈਟਿੰਗਾਂ ਅਤੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿੱਚ ਤੁਸੀਂ ਚੁਣੀਆਂ ਗਈਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਬਦਲ ਸਕਦੇ ਹੋ।"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"ਕੀ <xliff:g id="SERVICE">%1$s</xliff:g> ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"ਕੀ <xliff:g id="SERVICE">%1$s</xliff:g> ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ਕੁਝ ਸਕਿੰਟਾਂ ਲਈ ਦੋਵੇਂ ਅਵਾਜ਼ੀ ਕੁੰਜੀਆਂ ਨੂੰ ਦਬਾਈ ਰੱਖਣਾ <xliff:g id="SERVICE">%1$s</xliff:g>, ਇੱਕ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਚਾਲੂ ਕਰ ਦਿੰਦਾ ਹੈ। ਇਹ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੇ ਕੰਮ ਕਰਨ ਦੇ ਤਰੀਕੇ ਨੂੰ ਬਦਲ ਸਕਦਾ ਹੈ।\n\nਸੈਟਿੰਗਾਂ ਅਤੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿੱਚ ਤੁਸੀਂ ਇਸ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਕਿਸੇ ਹੋਰ ਵਿਸ਼ੇਸ਼ਤਾ ਵਿੱਚ ਬਦਲ ਸਕਦੇ ਹੋ।"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"ਚਾਲੂ ਕਰੋ"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"ਚਾਲੂ ਨਾ ਕਰੋ"</string> @@ -1829,8 +1839,7 @@ <item quantity="one">%d ਘੰਟਿਆਂ ਲਈ</item> <item quantity="other">%d ਘੰਟਿਆਂ ਲਈ</item> </plurals> - <!-- no translation found for zen_mode_until_next_day (1403042784161725038) --> - <skip /> + <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ਤੱਕ"</string> <string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ਤੱਕ"</string> <string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ਤੱਕ (ਅਗਲਾ ਅਲਾਰਮ)"</string> <string name="zen_mode_forever" msgid="740585666364912448">"ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਬੰਦ ਨਹੀਂ ਕਰਦੇ ਹੋ"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 16b3fafe3520..ca8522b032de 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -438,13 +438,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"zmienianie ustawień audio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Pozwala aplikacji na modyfikowanie globalnych ustawień dźwięku, takich jak głośność oraz urządzenie wyjściowe."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"nagrywanie dźwięku"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ta aplikacja może w dowolnym momencie nagrać dźwięk przez mikrofon."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"wysyłanie poleceń do karty SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Pozwala aplikacji na wysyłanie poleceń do karty SIM. To bardzo niebezpieczne."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"rozpoznawanie aktywności fizycznej"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ta aplikacja może rozpoznawać Twoją aktywność fizyczną."</string> <string name="permlab_camera" msgid="6320282492904119413">"wykonywanie zdjęć i filmów wideo"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Ta aplikacja może w dowolnym momencie robić zdjęcia i nagrywać filmy przy użyciu aparatu."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Zezwól na dostęp aplikacji lub usługi do aparatów systemu i robienie zdjęć oraz nagrywanie filmów"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ta aplikacja systemowa z podwyższonymi uprawnieniami może w dowolnym momencie robić zdjęcia i nagrywać filmy przy użyciu aparatu systemowego. Wymaga przyznania uprawnień android.permission.CAMERA również aplikacji."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Zezwól na dostęp aplikacji lub usługi na otrzymywanie wywoływania zwrotnego o urządzeniach z aparatem, kiedy są one uruchamiane lub zamykane."</string> @@ -1666,10 +1676,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Zwiększyć głośność ponad zalecany poziom?\n\nSłuchanie głośno przez długi czas może uszkodzić Twój słuch."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Użyć skrótu do ułatwień dostępu?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Gdy skrót jest włączony, jednoczesne naciskanie przez trzy sekundy obu przycisków głośności uruchamia funkcję ułatwień dostępu."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Włączyć ułatwienia dostępu?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Włączyć skrót ułatwień dostępu?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Przytrzymanie obu klawiszy głośności przez kilka sekund włącza ułatwienia dostępu. Może to zmienić sposób działania urządzenia.\n\nBieżące funkcje:\n<xliff:g id="SERVICE">%1$s</xliff:g>\naby zmienić wybrane funkcje, kliknij Ustawienia > Ułatwienia dostępu."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Włączyć usługę <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Włączyć skrót <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Przytrzymanie obu klawiszy głośności przez kilka sekund włącza usługę <xliff:g id="SERVICE">%1$s</xliff:g>, stanowiącą ułatwienie dostępu. Może to zmienić sposób działania urządzenia.\n\nAby zmienić ten skrót i wskazać inną funkcję, kliknij Ustawienia > Ułatwienia dostępu."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Włącz"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nie włączaj"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index 2883d6fd8ae2..f09e7b04bd44 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"alterar as suas configurações de áudio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que o app modifique configurações de áudio globais como volume e alto-falantes de saída."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"gravar áudio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Este app pode gravar áudio usando o microfone a qualquer momento."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos para o chip"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que o app envie comandos ao chip. Muito perigoso."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconhecer atividade física"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Este app pode reconhecer sua atividade física."</string> <string name="permlab_camera" msgid="6320282492904119413">"tirar fotos e gravar vídeos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Este app pode tirar fotos e gravar vídeos usando a câmera a qualquer momento."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Permitir que um aplicativo ou serviço acesse as câmeras do sistema para tirar fotos e gravar vídeos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Esse app do sistema ou com privilégios pode tirar fotos e gravar vídeos a qualquer momento usando a câmera do sistema. É necessário que o app tenha também a permissão android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permitir que um aplicativo ou serviço receba callbacks sobre dispositivos de câmera sendo abertos ou fechados."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Aumentar o volume acima do nível recomendado?\n\nOuvir em volume alto por longos períodos pode danificar sua audição."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Usar atalho de Acessibilidade?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quando o atalho estiver ativado, pressione os dois botões de volume por três segundos para iniciar um recurso de acessibilidade."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Ativar os recursos de acessibilidade?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Ativar atalho para recursos de acessibilidade?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Manter as duas teclas de volume pressionadas por alguns segundos ativa os recursos de acessibilidade. Isso pode mudar a forma como seu dispositivo funciona.\n\nRecursos atuais:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nÉ possível mudar os recursos selecionados em \"Config. > Acessibilidade\"."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Ativar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Ativar atalho para <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Manter as duas teclas de volume pressionadas por alguns segundos ativa o serviço <xliff:g id="SERVICE">%1$s</xliff:g>, um recurso de acessibilidade. Isso pode mudar a forma como seu dispositivo funciona.\n\nÉ possível trocar o uso desse atalho para outro recurso em \"Config. > Acessibilidade\"."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ativar"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Não ativar"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 3bc9530ab46e..c318a24c3bb8 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"alterar as suas definições de áudio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que a app modifique definições de áudio globais, tais como o volume e qual o altifalante utilizado para a saída de som."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"gravar áudio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Esta app pode gravar áudio através do microfone em qualquer altura."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos para o SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que a app envie comandos para o SIM. Esta ação é muito perigosa."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconhecer a atividade física"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Esta app consegue reconhecer a sua atividade física."</string> <string name="permlab_camera" msgid="6320282492904119413">"tirar fotos e vídeos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Esta app pode tirar fotos e gravar vídeos através da câmara em qualquer altura."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Permitir que uma app ou um serviço aceda às câmaras do sistema para tirar fotos e vídeos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Esta app do sistema ou privilegiada pode tirar fotos e gravar vídeos através de uma câmara do sistema em qualquer altura. Também necessita da autorização android.permission.CAMERA para a app."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permitir que uma app ou um serviço receba chamadas de retorno sobre dispositivos de câmara que estão a ser abertos ou fechados"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Aumentar o volume acima do nível recomendado?\n\nOuvir com um volume elevado durante longos períodos poderá ser prejudicial para a sua audição."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Pretende utilizar o atalho de acessibilidade?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quando o atalho está ativado, premir ambos os botões de volume durante 3 segundos inicia uma funcionalidade de acessibilidade."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Pretende ativar as funcionalidades de acessibilidade?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Pretende ativar o atalho das funcionalidades de acessibilidade?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Manter premidas ambas as teclas de volume durante alguns segundos ativa as funcionalidades de acessibilidade. Estas podem alterar a forma como o seu dispositivo funciona.\n\nFuncionalidades atuais:\n<xliff:g id="SERVICE">%1$s</xliff:g>\npode alterar as funcionalidades selecionadas em Definições > Acessibilidade."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Pretende ativar o serviço <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Pretende ativar o atalho do serviço <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Manter premidas ambas as teclas de volume durante alguns segundos ativa o serviço <xliff:g id="SERVICE">%1$s</xliff:g>, uma funcionalidade de acessibilidade. Esta pode alterar a forma como o seu dispositivo funciona.\n\nPode alterar este atalho para outra funcionalidade em Definições > Acessibilidade."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ativar"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Não ativar"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 2883d6fd8ae2..f09e7b04bd44 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"alterar as suas configurações de áudio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que o app modifique configurações de áudio globais como volume e alto-falantes de saída."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"gravar áudio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Este app pode gravar áudio usando o microfone a qualquer momento."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos para o chip"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que o app envie comandos ao chip. Muito perigoso."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconhecer atividade física"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Este app pode reconhecer sua atividade física."</string> <string name="permlab_camera" msgid="6320282492904119413">"tirar fotos e gravar vídeos"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Este app pode tirar fotos e gravar vídeos usando a câmera a qualquer momento."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Permitir que um aplicativo ou serviço acesse as câmeras do sistema para tirar fotos e gravar vídeos"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Esse app do sistema ou com privilégios pode tirar fotos e gravar vídeos a qualquer momento usando a câmera do sistema. É necessário que o app tenha também a permissão android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permitir que um aplicativo ou serviço receba callbacks sobre dispositivos de câmera sendo abertos ou fechados."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Aumentar o volume acima do nível recomendado?\n\nOuvir em volume alto por longos períodos pode danificar sua audição."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Usar atalho de Acessibilidade?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quando o atalho estiver ativado, pressione os dois botões de volume por três segundos para iniciar um recurso de acessibilidade."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Ativar os recursos de acessibilidade?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Ativar atalho para recursos de acessibilidade?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Manter as duas teclas de volume pressionadas por alguns segundos ativa os recursos de acessibilidade. Isso pode mudar a forma como seu dispositivo funciona.\n\nRecursos atuais:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nÉ possível mudar os recursos selecionados em \"Config. > Acessibilidade\"."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Ativar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Ativar atalho para <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Manter as duas teclas de volume pressionadas por alguns segundos ativa o serviço <xliff:g id="SERVICE">%1$s</xliff:g>, um recurso de acessibilidade. Isso pode mudar a forma como seu dispositivo funciona.\n\nÉ possível trocar o uso desse atalho para outro recurso em \"Config. > Acessibilidade\"."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ativar"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Não ativar"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index d5a9297c8298..0efa74ac97cf 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -435,13 +435,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modificare setări audio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite aplicației să modifice setările audio globale, cum ar fi volumul și difuzorul care este utilizat pentru ieșire."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"înregistreze sunet"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Această aplicație poate înregistra conținut audio folosind microfonul în orice moment."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"să trimită comenzi către SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite aplicației să trimită comenzi pe cardul SIM. Această permisiune este foarte periculoasă."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"recunoașterea activității fizice"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Această aplicație vă poate recunoaște activitatea fizică."</string> <string name="permlab_camera" msgid="6320282492904119413">"realizarea de fotografii și videoclipuri"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Această aplicație poate să facă fotografii și să înregistreze videoclipuri folosind camera foto în orice moment."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Permiteți unei aplicații sau unui serviciu accesul la camerele de sistem, ca să fotografieze și să înregistreze videoclipuri"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Această aplicație de sistem privilegiată poate să fotografieze și să înregistreze videoclipuri folosind o cameră de sistem în orice moment. Necesită și permisiunea android.permission.CAMERA pentru aplicație"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permiteți unei aplicații sau unui serviciu să primească apeluri inverse atunci când sunt deschise sau închise dispozitive cu cameră."</string> @@ -1644,10 +1654,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ridicați volumul mai sus de nivelul recomandat?\n\nAscultarea la volum ridicat pe perioade lungi de timp vă poate afecta auzul."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utilizați comanda rapidă pentru accesibilitate?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Atunci când comanda rapidă este activată, dacă apăsați ambele butoane de volum timp de trei secunde, veți lansa o funcție de accesibilitate."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Activați funcțiile de accesibilitate?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activați comanda rapidă pentru funcțiile de accesibilitate?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Dacă apăsați ambele taste de volum câteva secunde, activați funcțiile de accesibilitate. Acest lucru poate schimba funcționarea dispozitivului.\n\nFuncțiile actuale:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuteți schimba funcțiile selectate din Setări > Accesibilitate."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Activați <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activați comanda rapidă <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Dacă apăsați ambele taste de volum câteva secunde, activați funcția de accesibilitate <xliff:g id="SERVICE">%1$s</xliff:g>. Acest lucru poate schimba funcționarea dispozitivului.\n\nPuteți alege altă funcție pentru această comandă în Setări > Accesibilitate."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activați"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nu activați"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 3339fb52b72a..fc2fd35ace52 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -438,13 +438,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"Изменение настроек аудио"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Приложение сможет изменять системные настройки звука, например уровень громкости и активный динамик."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"Запись аудио"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Приложение может в любое время записывать аудио с помощью микрофона."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"Отправка команд SIM-карте"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Приложение сможет отправлять команды SIM-карте (данное разрешение представляет большую угрозу)."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"распознавать физическую активность"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Приложение может распознавать физическую активность."</string> <string name="permlab_camera" msgid="6320282492904119413">"Фото- и видеосъемка"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Приложение может в любое время делать фотографии и снимать видео с помощью камеры."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Доступ приложения или сервиса к системным настройкам камеры, позволяющим снимать фото и видео"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Это привилегированное или системное приложение может в любое время делать фотографии и записывать видео с помощью камеры. Для этого приложению также требуется разрешение android.permission.CAMERA."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Разрешить приложению или сервису получать обратные вызовы при открытии и закрытии камер"</string> @@ -1666,10 +1676,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Установить громкость выше рекомендуемого уровня?\n\nВоздействие громкого звука в течение долгого времени может привести к повреждению слуха."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Использовать быстрое включение?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Чтобы использовать функцию специальных возможностей, когда она включена, нажмите и удерживайте обе кнопки регулировки громкости в течение трех секунд."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Включить специальные возможности?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Использовать быстрое включение?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Чтобы включить специальные возможности, нажмите обе кнопки регулировки громкости и удерживайте несколько секунд. Обратите внимание, что в работе устройства могут произойти изменения.\n\nТекущие функции:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nЧтобы изменить выбранные функции, перейдите в настройки и нажмите \"Специальные возможности\"."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Включить функцию \"<xliff:g id="SERVICE">%1$s</xliff:g>\"?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Использовать быстрое включение сервиса \"<xliff:g id="SERVICE">%1$s</xliff:g>\"?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Чтобы включить функцию \"<xliff:g id="SERVICE">%1$s</xliff:g>\", нажмите обе кнопки регулировки громкости на несколько секунд. Обратите внимание, что в работе устройства могут произойти изменения.\n\nЧтобы назначить это сочетание клавиш другой функции, перейдите в настройки и выберите \"Специальные возможности\"."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Включить"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Не включать"</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index c26f23f3cc6d..6d88677c8a84 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ඔබගේ ශ්රව්ය සැකසීම් වෙනස් කරන්න"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ශබ්දය ආදී ගෝලීය ශබ්ද සැකසීම් වෙනස් කිරීමට සහ ප්රතිදානය සඳහා භාවිත කරන්නේ කුමන නාදකය දැයි තේරීමට යෙදුමට අවසර දෙන්න."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ශබ්ද පටිගත කරන්න"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"මෙම යෙදුමට ඕනෑම වේලාවක මයික්රෆෝනය භාවිතයෙන් ශ්රව්ය පටිගත කිරීමට හැකිය."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM වෙත විධාන යැවීම"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM වෙත විධාන ගෙන යාමට යෙදුමට අවසර දෙයි. මෙය ඉතා භයානක වේ."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"ශාරීරික ක්රියාකාරකම හඳුනා ගන්න"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"මෙම යෙදුමට ඔබේ ශාරීරික ක්රියාකාරකම හඳුනා ගැනීමට නොහැකිය"</string> <string name="permlab_camera" msgid="6320282492904119413">"පින්තූර සහ වීඩියෝ ගන්න"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"මෙම යෙදුමට ඕනෑම වේලාවක කැමරාව භාවිත කර පින්තූර ගැනීමට සහ වීඩියෝ පටිගත කිරීමට හැකිය."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"පින්තූර සහ වීඩියෝ ගැනීමට පද්ධති කැමරාවලට යෙදුමකට හෝ සේවාවකට ප්රවේශය ඉඩ දෙන්න"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"මෙම වරප්රසාද ලත් හෝ පද්ධති යෙදුමට ඕනෑම වේලාවක පද්ධති කැමරාව භාවිත කර පින්තූර ගැනීමට සහ වීඩියෝ පටිගත කිරීමට හැකිය. යෙදුම විසින් රඳවා තබා ගැනීමට android.permission.CAMERA ප්රවේශයද අවශ්ය වේ"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"විවෘත වෙමින් හෝ වැසෙමින් පවතින කැමරා උපාංග පිළිබඳ පසු ඇමතුම් ලබා ගැනීමට යෙදුමකට හෝ සේවාවකට ඉඩ දෙන්න."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"නිර්දේශිතයි මට්ටමට වඩා ශබ්දය වැඩිද?\n\nදිගු කාලයක් සඳහා ඉහළ ශබ්දයක් ඇසීමෙන් ඇතැම් විට ඔබගේ ඇසීමට හානි විය හැක."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ප්රවේශ්යතා කෙටිමඟ භාවිතා කරන්නද?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"කෙටිමග ක්රියාත්මක විට, හඬ පරිමා බොත්තම් දෙකම තත්පර 3ක් තිස්සේ එබීමෙන් ප්රවේශ්යතා විශේෂාංගය ආරම්භ වනු ඇත."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ප්රවේශ්යතා විශේෂාංග ක්රියාත්මක කරන්නද?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ප්රවේශ්යතා විශේෂාංග සඳහා කෙටි මග ක්රියාත්මක කරන්නද?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"හඬ පරිමා යතුරු දෙකම තත්පර කීපයකට පහළට අල්ලාගෙන සිටීම ප්රවේශ්යතා විශේෂාංග ක්රියාත්මක කරයි. මෙය ඔබේ උපාංගය ක්රියා කරන ආකාරය වෙනස් කළ හැකිය.\n\nවත්මන් විශේෂාංග:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nඔබට තේරූ විශේෂාංග සැකසීම් > ප්රවේශ්යතාව හි වෙනස් කළ හැකිය."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ක්රියාත්මක කරන්නද?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> කෙටි මග ක්රියාත්මක කරන්නද?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"හඬ පරිමා යතුරු දෙකම තත්පර කීපයකට පහළට අල්ලාගෙන සිටීම ප්රවේශ්යතා විශේෂාංගයක් වන <xliff:g id="SERVICE">%1$s</xliff:g> ක්රියාත්මක කරයි. මෙය ඔබේ උපාංගය ක්රියා කරන ආකාරය වෙනස් කළ හැකිය.\n\nඔබට මෙම කෙටිමග සැකසීම් > ප්රවේශ්යතාව හි තවත් විශේෂාංගයකට වෙනස් කළ හැකිය."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"ක්රියාත්මක කරන්න"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"ක්රියාත්මක නොකරන්න"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 1a4f07e397e6..5d7f8f64143c 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -438,13 +438,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"meniť nastavenia zvuku"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Umožňuje aplikácii upraviť globálne nastavenia zvuku, ako je hlasitosť, alebo určiť, z ktorého reproduktora bude zvuk vychádzať."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"nahrávať zvuk"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Táto aplikácia môže kedykoľvek nahrávať zvuk pomocou mikrofónu."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"posielanie príkazov do SIM karty"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Umožňuje aplikácii odosielať príkazy na SIM kartu. Toto je veľmi nebezpečné povolenie."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"rozpoznávanie fyzickej aktivity"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Táto aplikácia dokáže rozpoznať vašu fyzickú aktivitu."</string> <string name="permlab_camera" msgid="6320282492904119413">"fotiť a nakrúcať videá"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Táto aplikácia môže kedykoľvek fotografovať a zaznamenávať videá pomocou fotoaparátu."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Povoľte aplikácii alebo službe prístup k fotoaparátom systému na snímanie fotiek a videí"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Táto oprávnená alebo systémová aplikácia môže kedykoľvek fotiť a nahrávať videá fotoaparátom systému. Aplikácia musí mať tiež povolenie android.permission.CAMERA."</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Povoliť aplikácii alebo službe prijímať spätné volanie, keď sú zariadenia s kamerou otvorené alebo zatvorené."</string> @@ -1666,10 +1676,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Zvýšiť hlasitosť nad odporúčanú úroveň?\n\nDlhodobé počúvanie pri vysokej hlasitosti môže poškodiť váš sluch."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Použiť skratku dostupnosti?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Keď je skratka zapnutá, stlačením obidvoch tlačidiel hlasitosti na tri sekundy spustíte funkciu dostupnosti."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Chcete zapnúť funkcie dostupnosti?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Chcete zapnúť skratku pre funkcie dostupnosti?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Pridržaním oboch tlačidiel hlasitosti na niekoľko sekúnd zapnete funkcie dostupnosti. Môže sa tým zmeniť spôsob fungovania vášho zariadenia.\n\nAktuálne funkcie:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nVybrané funkcie môžete zmeniť v časti Nastavenia > Dostupnosť."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Chcete zapnúť <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Chcete zapnúť skratku na službu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Pridržaním oboch klávesov hlasitosti na niekoľko sekúnd zapnete funkciu dostupnosti <xliff:g id="SERVICE">%1$s</xliff:g>. Môže sa tým zmeniť spôsob fungovania vášho zariadenia.\n\nTúto skratku môžete zmeniť na inú funkciu v časti Nastavenia > Dostupnosť."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Zapnúť"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nezapínať"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 0079e389116f..7e6e300776f3 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -438,13 +438,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"spreminjanje nastavitev zvoka"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Aplikaciji omogoča spreminjanje splošnih zvočnih nastavitev, na primer glasnost in kateri zvočnik se uporablja."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"snemanje zvoka"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ta aplikacija lahko poljubno uporablja mikrofon za snemanje zvoka."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"pošiljanje ukazov na kartico SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Aplikaciji dovoli pošiljanje ukazov kartici SIM. To je lahko zelo nevarno."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje telesne dejavnosti"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ta aplikacija lahko prepoznava vašo telesno dejavnost."</string> <string name="permlab_camera" msgid="6320282492904119413">"fotografiranje in snemanje videoposnetkov"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Ta aplikacija lahko poljubno uporablja fotoaparat za snemanje fotografij ali videoposnetkov."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Aplikaciji ali storitvi dovoli dostop do vgrajenih fotoaparatov za snemanje fotografij in videoposnetkov"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ta prednostna ali sistemska aplikacija lahko z vgrajenim fotoaparatom kadar koli snema fotografije in videoposnetke. Aplikacija mora imeti omogočeno tudi dovoljenje android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Aplikaciji ali storitvi dovoli prejemanje povratnih klicev o odpiranju ali zapiranju naprav s fotoaparati."</string> @@ -1666,10 +1676,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ali želite povečati glasnost nad priporočeno raven?\n\nDolgotrajno poslušanje pri veliki glasnosti lahko poškoduje sluh."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite uporabljati bližnjico funkcij za ljudi s posebnimi potrebami?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Ko je bližnjica vklopljena, pritisnite gumba za glasnost in ju pridržite tri sekunde, če želite zagnati funkcijo za ljudi s posebnimi potrebami."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Želite vklopiti funkcije za ljudi s posebnimi potrebami?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Želite vklopiti bližnjico za funkcije za ljudi s posebnimi potrebami?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Če za nekaj sekund pridržite obe tipki za glasnost, boste vklopili funkcije za ljudi s posebnimi potrebami. To lahko spremeni način delovanja naprave.\n\nTrenutne funkcije:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nIzbrane funkcije lahko spremenite v meniju »Nastavitve« > »Funkcije za ljudi s posebnimi potrebami«."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Želite vklopiti storitev <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Želite vklopiti bližnjico za <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Če za nekaj sekund pridržite obe tipki za glasnost, boste vklopili storitev <xliff:g id="SERVICE">%1$s</xliff:g>, ki je funkcija za ljudi s posebnimi potrebami. To lahko spremeni način delovanja naprave.\n\nTo bližnjico lahko v meniju »Nastavitve« > »Funkcije za ljudi s posebnimi potrebami« spremenite, da bo uporabljena za drugo funkcijo."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Vklopi"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ne vklopi"</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index b29582c05d6b..f6ef6e9bbfbe 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ndrysho cilësimet e audios"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Lejon aplikacionin të modifikojë cilësimet globale të audios siç është volumi dhe se cili altoparlant përdoret për daljen."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"regjistro audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ky aplikacion mund të regjistrojë audio me mikrofonin në çdo kohë."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"dërgo komanda te karta SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Lejon aplikacionin t\'i dërgojë komanda kartës SIM. Kjo është shumë e rrezikshme."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"njih aktivitetin fizik"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ky aplikacion mund të njohë aktivitetin tënd fizik."</string> <string name="permlab_camera" msgid="6320282492904119413">"bëj fotografi dhe video"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Ky aplikacion mund të nxjerrë fotografi dhe të regjistrojë video me kamerën tënde në çdo kohë."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Lejo një aplikacion ose shërbim të ketë qasje në kamerat e sistemit për të shkrepur fotografi dhe regjistruar video"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ky aplikacion sistemi ose i privilegjuar mund të nxjerrë fotografi dhe të regjistrojë video duke përdorur një kamerë në çdo moment. Kërkon që autorizimi i android.permission.CAMERA të mbahet edhe nga aplikacioni"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Lejo që një aplikacion ose shërbim të marrë telefonata mbrapsht për pajisjet e kamerës që hapen ose mbyllen."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Të ngrihet volumi mbi nivelin e rekomanduar?\n\nDëgjimi me volum të lartë për periudha të gjata mund të dëmtojë dëgjimin."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Të përdoret shkurtorja e qasshmërisë?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kur shkurtorja është e aktivizuar, shtypja e të dy butonave për 3 sekonda do të nisë një funksion qasshmërie."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Të aktivizohen veçoritë e qasshmërisë?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Të aktivizohet shkurtorja për veçoritë e qasshmërisë?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Mbajtja shtypur e dy tasteve të volumit për pak sekonda aktivizon veçoritë e qasshmërisë. Kjo mund të ndryshojë mënyrën se si funksionon pajisja jote.\n\nVeçoritë aktuale:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nKe ndryshuar veçoritë e zgjedhura te Cilësimet > Qasshmëria."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Të aktivizohet <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Të aktivizohet shkurtorja për <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Mbajtja shtypur e dy tasteve të volumit për pak sekonda aktivizon <xliff:g id="SERVICE">%1$s</xliff:g>, një veçori të qasshmërisë. Kjo mund të ndryshojë mënyrën se si funksionon pajisja jote.\n\nMund të ndryshosh këtë shkurtore te një veçori tjetër te Cilësimet > Qasshmëria."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivizo"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Mos e aktivizo"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index f24e285700c5..d799ae90afcf 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -435,13 +435,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"промена аудио подешавања"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Дозвољава апликацији да мења глобална аудио подешавања као што су јачина звука и избор звучника који се користи као излаз."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"снимање аудио записа"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ова апликација може да снима звук помоћу микрофона у било ком тренутку."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"слање команди на SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Омогућава апликацији да шаље команде SIM картици. То је веома опасно."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"препознавање физичких активности"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ова апликација може да препозна физичке активности."</string> <string name="permlab_camera" msgid="6320282492904119413">"снимање фотографија и видео снимака"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Ова апликација може да снима фотографије и видео снимке помоћу камере у било ком тренутку."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Дозволите некој апликацији или услузи да приступа камерама система да би снимала слике и видео снимке"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ова привилегована системска апликација може да снима слике и видео снимке помоћу камере система у било ком тренутку. Апликација треба да има и дозволу android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Дозволите апликацији или услузи да добија повратне позиве о отварању или затварању уређаја са камером."</string> @@ -1644,10 +1654,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Желите да појачате звук изнад препорученог нивоа?\n\nСлушање гласне музике дуже време може да вам оштети слух."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Желите ли да користите пречицу за приступачност?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Када је пречица укључена, притисните оба дугмета за јачину звука да бисте покренули функцију приступачности."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Желите ли да укључите функције приступачности?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Желите да укључите пречицу за функције приступачности?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ако задржите оба тастера за јачину звука пар секунди, укључиће се функције приступачности. То може да промени начин рада уређаја.\n\nПостојеће функције:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nМожете да промените изабране функције у одељку Подешавања > Приступачност."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Желите ли да укључите услугу <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Желите да укључите пречицу за услугу <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ако задржите оба тастера за јачину звука пар секунди, укључује се <xliff:g id="SERVICE">%1$s</xliff:g>, функција приступачности. То може да промени начин рада уређаја.\n\nМожете да промените функцију на коју се односи ова пречица у одељку Подешавања > Приступачност."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Укључи"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Не укључуј"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 5c9ce2c8a608..318f5a811672 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ändra dina ljudinställningar"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Tillåter att appen ändrar globala ljudinställningar som volym och vilken högtalarutgång som används."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"spela in ljud"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Appen kan spela in ljud med mikrofonen när som helst."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"skicka kommandon till SIM-kortet"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Tillåter att appen skickar kommandon till SIM-kortet. Detta är mycket farligt."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"känn igen fysisk aktivitet"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Den här appen kan känna igen fysisk aktivitet."</string> <string name="permlab_camera" msgid="6320282492904119413">"ta bilder och spela in videoklipp"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Appen kan ta kort och spela in video med kameran när som helst."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Ge en app eller tjänst behörighet att ta bilder och spela in videor med systemets kameror"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Denna systemapp med särskild behörighet kan ta bilder och spela in videor med systemets kamera när som helst. Appen måste även ha behörigheten android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Tillåt att en app eller tjänst får återanrop när en kameraenhet öppnas eller stängs."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vill du höja volymen över den rekommenderade nivån?\n\nAtt lyssna med stark volym långa stunder åt gången kan skada hörseln."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vill du använda Aktivera tillgänglighet snabbt?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"När kortkommandot har aktiverats startar du en tillgänglighetsfunktion genom att trycka ned båda volymknapparna i tre sekunder."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Vill du aktivera tillgänglighetsfunktionerna?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vill du aktivera genvägen till tillgänglighetsfunktioner?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Om du trycker ned båda volymknapparna i ett par sekunder aktiveras tillgänglighetsfunktionerna. Det kan få enheten ett fungera annorlunda.\n\nAktuella funktioner:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kan ändra vilka funktioner som aktiveras under Inställningar > Tillgänglighet."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Vill du aktivera <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vill du aktivera genvägen till <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Om du trycker ned båda volymknapparna i ett par sekunder aktiveras <xliff:g id="SERVICE">%1$s</xliff:g>, en tillgänglighetsfunktion. Det kan leda till att enheten fungerar annorlunda.\n\nDu kan ändra vilken funktion som ska aktiveras med genvägen under Inställningar > Tillgänglighet."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivera"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Aktivera inte"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 55db3eb45225..a59499f8bc36 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"badilisha mipangilio yako ya sauti"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Inaruhusu programu kurekebisha mipangilio ya sauti kila mahali kama vile sauti na ni kipaza sauti kipi ambacho kinatumika kwa kutoa."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"kurekodi sauti"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Programu hii inaweza kurekodi sauti kwa kutumia maikrofoni wakati wowote."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"tuma amri kwenye SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Huruhusu programu kutuma amri kwa SIM. Hii ni hatari sana."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"itambue shughuli unazofanya"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Programu hii inaweza kutambua shughuli unazofanya."</string> <string name="permlab_camera" msgid="6320282492904119413">"Kupiga picha na kurekodi video"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Programu hii inaweza kupiga picha na kurekodi video kwa kutumia kamera wakati wowote."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Ruhusu programu au huduma ifikie kamera za mfumo ili ipige picha na irekodi video"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Programu hii ya mfumo au inayopendelewa inaweza kupiga picha na kurekodi video ikitumia kamera ya mfumo wakati wowote. Inahitaji ruhusa ya android.permission.CAMERA iwepo kwenye programu pia"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Ruhusu programu au huduma ipokee simu zinazopigwa tena kuhusu vifaa vya kamera kufunguliwa au kufungwa."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ungependa kupandisha sauti zaidi ya kiwango kinachopendekezwa?\n\nKusikiliza kwa sauti ya juu kwa muda mrefu kunaweza kuharibu uwezo wako wa kusikia."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Ungependa kutumia njia ya mkato ya ufikivu?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Unapowasha kipengele cha njia ya mkato, hatua ya kubonyeza vitufe vyote viwili vya sauti kwa sekunde tatu itafungua kipengele cha ufikivu."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Ungependa kuwasha vipengele vya ufikivu?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Ungependa kuwasha njia ya mkato ya vipengele vya ufikivu?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hatua ya kushikilia chini vitufe vyote viwili vya sauti kwa sekunde chache huwasha vipengele vya ufikivu. Huenda hatua hii ikabadilisha jinsi kifaa chako kinavyofanya kazi.\n\nVipengele vya sasa:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nUnaweza kubadilisha vipengele ulivyochagua katika Mipangilio > Ufikivu."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Ungependa kuwasha <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Ungependa kuwasha njia ya mkato ya <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Hatua ya kushikilia chini vitufe vyote viwili vya sauti kwa sekunde chache huwasha <xliff:g id="SERVICE">%1$s</xliff:g>, kipengele cha ufikivu. Huenda hatua hii ikabadilisha jinsi kifaa chako kinavyofanya kazi.\n\nUnaweza kubadilisha njia hii ya mkato iwe kipengele kingine katika Mipangilio > Ufikivu."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Washa"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Usiwashe"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index be030d16b1ed..49e6c7c3ba0a 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"எனது ஆடியோ அமைப்புகளை மாற்றுதல்"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ஒலியளவு மற்றும் வெளியீட்டிற்கு ஸ்பீக்கர்கள் பயன்படுத்தப்படுவது போன்ற ஒட்டுமொத்த ஆடியோ அமைப்புகளைக் கட்டுப்படுத்தப் ஆப்ஸை அனுமதிக்கிறது."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ஆடியோவைப் பதிவுசெய்தல்"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"இந்த ஆப்ஸ் எப்போது வேண்டுமானாலும் மைக்ரோஃபோனைப் பயன்படுத்தி ஆடியோவை ரெக்கார்டு செய்யலாம்."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"கட்டளைகளை சிம்மிற்கு அனுப்புதல்"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"சிம் க்குக் கட்டளைகளை அனுப்ப ஆப்ஸை அனுமதிக்கிறது. இது மிகவும் ஆபத்தானதாகும்."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"உடல் செயல்பாட்டைக் கண்டறிதல்"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"உங்கள் உடல் செயல்பாட்டை இந்த ஆப்ஸால் கண்டறிய முடியும்."</string> <string name="permlab_camera" msgid="6320282492904119413">"படங்கள் மற்றும் வீடியோக்களை எடுத்தல்"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"இந்த ஆப்ஸ் எப்போது வேண்டுமானாலும் கேமராவைப் பயன்படுத்தி படங்களை எடுக்கலாம், வீடியோக்களை ரெக்கார்டு செய்யலாம்."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"படங்களையும் வீடியோக்களையும் எடுப்பதற்கு சிஸ்டம் கேமராக்களை அணுக ஆப்ஸையோ சேவையையோ அனுமதி"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"இந்த முன்னுரிமை பெற்ற அல்லது சிஸ்டம் ஆப்ஸால் சிஸ்டம் கேமராவைப் பயன்படுத்தி எப்போது வேண்டுமானாலும் படங்களை எடுக்கவோ வீடியோக்களை ரெக்கார்டு செய்யவோ முடியும். android.permission.CAMERA அனுமதியும் ஆப்ஸிற்குத் தேவை"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"கேமரா சாதனங்கள் திறக்கப்படும்போதோ மூடப்படும்போதோ அது குறித்த கால்பேக்குகளைப் பெற ஒரு ஆப்ஸையோ சேவையையோ அனுமதிக்கவும்."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"பரிந்துரைத்த அளவை விட ஒலியை அதிகரிக்கவா?\n\nநீண்ட நேரத்திற்கு அதிகளவில் ஒலி கேட்பது கேட்கும் திறனைப் பாதிக்கலாம்."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"அணுகல்தன்மை ஷார்ட்கட்டைப் பயன்படுத்தவா?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ஷார்ட்கட் இயக்கத்தில் இருக்கும்போது ஒலியளவு பட்டன்கள் இரண்டையும் 3 வினாடிகளுக்கு அழுத்தினால் அணுகல்தன்மை அம்சம் இயக்கப்படும்."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"அணுகல்தன்மை அம்சங்களை ஆன் செய்யவா?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"அணுகல்தன்மை அம்சங்களுக்கான ஷார்ட்கட்டை ஆன் செய்யவா?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"இரண்டு ஒலியளவு விசைகளையும் சில விநாடிகள் பிடித்திருந்தால் அணுகல்தன்மை அம்சங்கள் ஆன் செய்யப்படும். இதனால் உங்கள் சாதனம் வேலை செய்யும் முறை மாறக்கூடும்.\n\nதற்போதைய அம்சங்கள்:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nதேர்ந்தெடுத்த அம்சங்களை அமைப்புகள் > அணுகல்தன்மைக்குச் சென்று உங்களால் மாற்ற முடியும்."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ஐ ஆன் செய்யவா?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> அம்சத்துக்கான ஷார்ட்கட்டை ஆன் செய்யவா?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"இரண்டு ஒலியளவு விசைகளையும் சில விநாடிகள் பிடித்திருப்பதால் அணுகல்தன்மை அம்சமான <xliff:g id="SERVICE">%1$s</xliff:g> ஆன் ஆகும். இதனால் உங்கள் சாதனம் வேலை செய்யும் முறை மாறக்கூடும்.\n\nஅமைப்புகள் > அணுகல்தன்மைக்குச் சென்று இந்த ஷார்ட்கட்டை வேறு அம்சத்திற்கு மாற்ற முடியும்."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"ஆன் செய்"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"ஆன் செய்யாதே"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 0966d235f5dd..caaf8a90e1b2 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"మీ ఆడియో సెట్టింగ్లను మార్చడం"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"వాల్యూమ్ మరియు అవుట్పుట్ కోసం ఉపయోగించాల్సిన స్పీకర్ వంటి సార్వజనీన ఆడియో సెట్టింగ్లను సవరించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ఆడియోను రికార్డ్ చేయడం"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"ఈ యాప్ మైక్రోఫోన్ని ఉపయోగించి ఎప్పుడైనా ఆడియోను రికార్డ్ చేయగలదు."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIMకి ఆదేశాలను పంపడం"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"సిమ్కు ఆదేశాలను పంపడానికి అనువర్తనాన్ని అనుమతిస్తుంది. ఇది చాలా ప్రమాదకరం."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"భౌతిక కార్యాకలాపాన్ని గుర్తించండి"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"ఈ యాప్ మీ భౌతిక కార్యాకలాపాన్ని గుర్తించగలదు."</string> <string name="permlab_camera" msgid="6320282492904119413">"చిత్రాలు మరియు వీడియోలు తీయడం"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"ఈ యాప్ కెమెరాను ఉపయోగించి ఎప్పుడైనా చిత్రాలను తీయగలదు మరియు వీడియోలను రికార్డ్ చేయగలదు."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"ఫోటోలు, వీడియోలు తీయడానికి సిస్టమ్ కెమెరాలకు యాప్, లేదా సేవా యాక్సెస్ను అనుమతించండి"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"ఈ విశేష లేదా సిస్టమ్ యాప్ ఎప్పుడైనా సిస్టమ్ కెమెరాను ఉపయోగించి ఫోటోలు తీయగలదు, వీడియోలను రికార్డ్ చేయగలదు. యాప్కు android.permission.CAMERA అనుమతి ఇవ్వడం కూడా అవసరం"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"కెమెరా పరికరాలు తెరుచుకుంటున్నప్పుడు లేదా మూసుకుంటున్నప్పుడు కాల్బ్యాక్లను స్వీకరించడానికి యాప్ను లేదా సర్వీస్ను అనుమతించండి."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"వాల్యూమ్ను సిఫార్సు చేయబడిన స్థాయి కంటే ఎక్కువగా పెంచాలా?\n\nసుదీర్ఘ వ్యవధుల పాటు అధిక వాల్యూమ్లో వినడం వలన మీ వినికిడి శక్తి దెబ్బ తినవచ్చు."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"యాక్సెస్ సామర్థ్యం షార్ట్కట్ను ఉపయోగించాలా?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"షార్ట్కట్ ఆన్ చేసి ఉన్నప్పుడు, రెండు వాల్యూమ్ బటన్లను 3 సెకన్ల పాటు నొక్కి ఉంచితే యాక్సెస్ సౌలభ్య ఫీచర్ ప్రారంభం అవుతుంది."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"యాక్సెసిబిలిటీ ఫీచర్లను ఆన్ చేయాలా?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"యాక్సెస్ సౌలభ్య ఫీచర్ల కోసం షార్ట్కట్ను ఆన్ చేయాలా?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"రెండు వాల్యూమ్ కీలను కొంత సేపు నొక్కి పట్టుకుంటే యాక్సెసిబిలిటీ ఫీచర్లు ఆన్ అవుతాయి. ఇది మీ పరికరం పని చేసే విధానాన్ని మార్చవచ్చు.\n\nప్రస్తుత ఫీచర్లు:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nఎంపిక చేసిన ఫీచర్లను మీరు సెట్టింగ్లు>యాక్సెసిబిలిటీలో మార్చవచ్చు."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ఆన్ చేయాాలా?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> షార్ట్కట్ను ఆన్ చేయాలా?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"రెండు వాల్యూమ్ కీలను కొన్ని సెకన్ల పాటు నొక్కి పట్టుకోవడం ద్వారా యాక్సెసిబిలిటీ అయిన <xliff:g id="SERVICE">%1$s</xliff:g> ఆన్ అవుతుంది. ఇది మీ పరికరం పని చేసే విధానాన్ని మార్చవచ్చు.\n\nసెట్టింగ్లు > యాక్సెసిబిలిటీలో, వేరొక ఫీచర్ను ప్రారంభించేలా ఈ షార్ట్ కట్ను మీరు మార్చవచ్చు."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"ఆన్ చేయి"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"ఆన్ చేయకండి"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 7ff76ec7bde6..8c1917f49a69 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"เปลี่ยนการตั้งค่าเสียงของคุณ"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"อนุญาตให้แอปพลิเคชันปรับเปลี่ยนการตั้งค่าเสียงทั้งหมดได้ เช่น ระดับเสียงและลำโพงที่จะใช้งาน"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"บันทึกเสียง"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"แอปนี้สามารถบันทึกเสียงด้วยไมโครโฟนได้ทุกเมื่อ"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"ส่งคำสั่งไปยังซิม"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"อนุญาตให้แอปส่งคำสั่งไปยัง SIM ซึ่งอันตรายมาก"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"จดจำกิจกรรมการเคลื่อนไหวร่างกาย"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"แอปนี้จดจำกิจกรรมการเคลื่อนไหวร่างกายของคุณได้"</string> <string name="permlab_camera" msgid="6320282492904119413">"ถ่ายภาพและวิดีโอ"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"แอปนี้สามารถถ่ายภาพและวิดีโอด้วยกล้องได้ทุกเมื่อ"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"อนุญาตให้แอปพลิเคชันหรือบริการเข้าถึงกล้องของระบบเพื่อถ่ายภาพและวิดีโอ"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"แอปของระบบหรือที่ได้รับสิทธิ์นี้จะถ่ายภาพและบันทึกวิดีโอโดยใช้กล้องของระบบได้ทุกเมื่อ แอปต้องมีสิทธิ์ android.permission.CAMERA ด้วย"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"อนุญาตให้แอปพลิเคชันหรือบริการได้รับโค้ดเรียกกลับเมื่อมีการเปิดหรือปิดอุปกรณ์กล้อง"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"นี่เป็นการเพิ่มระดับเสียงเกินระดับที่แนะนำ\n\nการฟังเสียงดังเป็นเวลานานอาจทำให้การได้ยินของคุณบกพร่องได้"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ใช้ทางลัดการช่วยเหลือพิเศษไหม"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"เมื่อทางลัดเปิดอยู่ การกดปุ่มปรับระดับเสียงทั้ง 2 ปุ่มนาน 3 วินาทีจะเริ่มฟีเจอร์การช่วยเหลือพิเศษ"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ต้องการเปิดฟีเจอร์การช่วยเหลือพิเศษใช่ไหม"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"เปิดใช้ทางลัดสำหรับฟีเจอร์การช่วยเหลือพิเศษใช่ไหม"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"การกดปุ่มปรับระดับเสียงทั้ง 2 ปุ่มค้างไว้ 2-3 วินาทีจะเปิดฟีเจอร์การช่วยเหลือพิเศษ การดำเนินการนี้อาจเปลี่ยนแปลงลักษณะการทำงานของอุปกรณ์\n\nฟีเจอร์ปัจจุบัน:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nคุณจะเปลี่ยนฟีเจอร์ที่เลือกไว้ได้ในการตั้งค่า > การช่วยเหลือพิเศษ"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"ต้องการเปิด <xliff:g id="SERVICE">%1$s</xliff:g> ใช่ไหม"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"เปิดใช้ทางลัด <xliff:g id="SERVICE">%1$s</xliff:g> ใช่ไหม"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"การกดปุ่มปรับระดับเสียงทั้ง 2 ปุ่มค้างไว้ 2-3 วินาทีจะเปิด <xliff:g id="SERVICE">%1$s</xliff:g> ซึ่งเป็นฟีเจอร์การช่วยเหลือพิเศษ การดำเนินการนี้อาจเปลี่ยนแปลงลักษณะการทำงานของอุปกรณ์\n\nคุณแก้ไขทางลัดนี้ให้เปิดฟีเจอร์อื่นได้ในการตั้งค่า > การช่วยเหลือพิเศษ"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"เปิด"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"ไม่ต้องเปิด"</string> @@ -2031,7 +2041,7 @@ <item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> และอีก <xliff:g id="COUNT_3">%d</xliff:g> ไฟล์</item> <item quantity="one"><xliff:g id="FILE_NAME_0">%s</xliff:g> และอีก <xliff:g id="COUNT_1">%d</xliff:g> ไฟล์</item> </plurals> - <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"ไม่มีบุคคลที่แนะนำให้แชร์ด้วย"</string> + <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"ไม่พบใครที่แนะนำให้แชร์ด้วย"</string> <string name="chooser_all_apps_button_label" msgid="3230427756238666328">"รายชื่อแอป"</string> <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"แอปนี้ไม่ได้รับอนุญาตให้บันทึกเสียงแต่จะบันทึกเสียงผ่านอุปกรณ์ USB นี้ได้"</string> <string name="accessibility_system_action_home_label" msgid="3234748160850301870">"หน้าแรก"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 7d9c7173c84e..af0073b3aa9e 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"baguhin ang mga setting ng iyong audio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Pinapayagan ang app na baguhin ang mga pandaigdigang setting ng audio gaya ng volume at kung aling speaker ang ginagamit para sa output."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"mag-record ng audio"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Makakapag-record ng audio ang app na ito gamit ang mikropono anumang oras."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"magpadala ng mga command sa SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Pinapahintulutang magpadala ang app ng mga command sa SIM. Napakapanganib nito."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"tukuyin ang pisikal na aktibidad"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Matutukoy ng app na ito ang iyong pisikal na aktibidad."</string> <string name="permlab_camera" msgid="6320282492904119413">"kumuha ng mga larawan at video"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Makakakuha ng mga larawan at makakapag-record ng mga video ang app na ito gamit ang camera anumang oras."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Bigyan ang isang application o serbisyo ng access sa mga camera ng system para kumuha ng mga larawan at video"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ang may pribilehiyong app o system app na ito ay makakakuha ng mga larawan at makakapag-record ng mga video gamit ang isang camera ng system anumang oras. Kinakailangang may android.permission.CAMERA na pahintulot din ang app"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Payagan ang isang application o serbisyo na makatanggap ng mga callback tungkol sa pagbubukas o pagsasara ng mga camera device."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Lakasan ang volume nang lagpas sa inirerekomendang antas?\n\nMaaaring mapinsala ng pakikinig sa malakas na volume sa loob ng mahahabang panahon ang iyong pandinig."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gagamitin ang Shortcut sa Accessibility?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kapag naka-on ang shortcut, magsisimula ang isang feature ng pagiging naa-access kapag pinindot ang parehong button ng volume."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"I-on ang mga feature ng accessibility?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"I-on ang shortcut para sa mga feature ng pagiging naa-access?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Mao-on ang mga feature ng accessibility kapag pinindot nang matagal ang parehong volume key nang ilang segundo. Posibleng mabago nito ang paggana ng iyong device.\n\nMga kasalukuyang feature:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuwede mong baguhin ang mga napiling feature sa Mga Setting > Accessibility."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"I-on ang <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"I-on ang shortcut ng <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Mao-on ang feature ng accessibility na <xliff:g id="SERVICE">%1$s</xliff:g> kapag pinindot nang matagal ang parehong volume key nang ilang segundo. Posibleng mabago nito ang paggana ng iyong device.\n\nPuwede mong palitan ng ibang feature ang shortcut na ito sa Mga Setting > Accessibility."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"I-on"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Huwag i-on"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index a658bf18ab95..c0b28310dde6 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ses ayarlarınızı değiştirin"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Uygulamaya ses düzeyi ve ses çıkışı için kullanılan hoparlör gibi genel ses ayarlarını değiştirme izni verir."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ses kaydet"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Bu uygulama, istediği zaman mikrofonu kullanarak ses kaydedebilir."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM karta komut gönderme"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Uygulamanın SIM karta komut göndermesine izin verir. Bu izin çok tehlikelidir."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"fiziksel aktiviteyi algıla"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Bu uygulama fiziksel aktivitenizi algılayabilir."</string> <string name="permlab_camera" msgid="6320282492904119413">"resim çekme ve görüntü kaydetme"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Bu uygulama, herhangi bir zamanda kamerayı kullanarak fotoğraf ve video çekebilir."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Bir uygulama veya hizmetin fotoğraf ve video çekmek için sistem kameralarına erişmesine izin verin"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ayrıcalık tanınmış bu veya sistem uygulaması herhangi bir zamanda sistem kamerası kullanarak fotoğraf çekebilir ve video kaydedebilir. Uygulamanın da bu ayrıcalığa sahip olması için android.permission.CAMERA izni gerektirir"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Bir uygulama veya hizmetin açılıp kapatılan kamera cihazları hakkında geri çağırmalar almasına izin verin."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ses seviyesi önerilen düzeyin üzerine yükseltilsin mi?\n\nUzun süre yüksek ses seviyesinde dinlemek işitme duyunuza zarar verebilir."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Erişilebilirlik Kısayolu Kullanılsın mı?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kısayol açıkken ses düğmelerinin ikisini birden 3 saniyeliğine basılı tutmanız bir erişilebilirlik özelliğini başlatır."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Erişilebilirlik özellikleri etkinleştirilsin mi?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Erişilebilirlik özellikleri için kısayol açılsın mı?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ses tuşlarının ikisini birden birkaç saniyeliğine basılı tutmak, erişilebilirlik özelliklerini açar. Bu, cihazınızın çalışma şeklini değiştirebilir.\n\nGeçerli özellikler:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nSeçilen özellikleri Ayarlar > Erişilebilirlik\'te değiştirebilirsiniz."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> etkinleştirilsin mi?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> kısayolu açılsın mı?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ses tuşlarının ikisini birden birkaç saniyeliğine basılı tutmak <xliff:g id="SERVICE">%1$s</xliff:g> erişilebilirlik özelliğini etkinleştirir. Bu, cihazınızın çalışma şeklini değiştirebilir.\n\nBu kısayolu, Ayarlar > Erişilebilirlik\'te başka bir özellikle değiştirebilirsiniz."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Etkinleştir"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Etkinleştirme"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 7e81784d1043..4c5e13f14887 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -438,13 +438,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"змінювати налаштув-ня звуку"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Дозволяє програмі змінювати загальні налаштування звуку, як-от гучність і динамік, який використовується для виводу сигналу."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"запис-ти аудіо"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Цей додаток може будь-коли записувати звук за допомогою мікрофона."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"надсилати команди на SIM-карту"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Дозволяє програмі надсилати команди на SIM-карту. Це дуже небезпечно."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"розпізнавати фізичну активність"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Цей додаток може розпізнавати фізичну активність."</string> <string name="permlab_camera" msgid="6320282492904119413">"фотограф. та знімати відео"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Цей додаток може будь-коли робити фотографії та записувати відео за допомогою камери."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Дозволити додатку або сервісу отримувати доступ до системних камер, робити фото й записувати відео"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Цей пріоритетний системний додаток може будь-коли робити фото й записувати відео, використовуючи камеру системи. Додатку потрібен дозвіл android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Дозволити додатку або сервісу отримувати зворотні виклики щодо відкриття чи закриття камер."</string> @@ -1666,10 +1676,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Збільшити гучність понад рекомендований рівень?\n\nЯкщо слухати надто гучну музику тривалий час, можна пошкодити слух."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Використовувати швидке ввімкнення?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Якщо цей засіб увімкнено, ви можете активувати спеціальні можливості, утримуючи обидві кнопки гучності протягом трьох секунд."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Увімкнути спеціальні можливості?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Увімкнути засіб спеціальних можливостей?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Якщо втримувати обидві клавіші гучності впродовж кількох секунд, вмикаються спеціальні можливості. Це впливає на роботу пристрою.\n\nПоточні функції:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nВибрані функції можна змінити в налаштуваннях у меню спеціальних можливостей."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Увімкнути <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Увімкнути засіб швидкого доступу до сервісу <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Якщо втримувати обидві клавіші гучності впродовж кількох секунд, буде ввімкнено спеціальні можливості – <xliff:g id="SERVICE">%1$s</xliff:g>. Це може вплинути на роботу пристрою.\n\nДля цієї комбінації клавіш можна вибрати іншу функцію в меню \"Налаштування > Спеціальні можливості\"."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Увімкнути"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Не вмикати"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index b9b9c586d8bb..7809d5b6acf5 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"اپنے آڈیو کی ترتیبات کو تبدیل کریں"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ایپ کو مجموعی آڈیو ترتیبات جیسے والیوم اور آؤٹ پٹ کیلئے جو اسپیکر استعمال ہوتا ہے اس میں ترمیم کرنے کی اجازت دیتا ہے۔"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"آڈیو ریکارڈ کریں"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"یہ ایپ کسی بھی وقت مائیکروفون استعمال کرتے ہوئے آڈیو ریکارڈ کر سکتی ہے۔"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM کو ہدایات بھیجیں"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"ایپ کو SIM کو کمانڈز بھیجنے کی اجازت دیتا ہے۔ یہ بہت خطرناک ہے۔"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"جسمانی سرگرمی کی شناخت کریں"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"یہ ایپ آپ کی جسمانی سرگرمی کی شناخت کر سکتی ہے۔"</string> <string name="permlab_camera" msgid="6320282492904119413">"تصاویر لیں اور ویڈیوز بنائیں"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"یہ ایپ کسی بھی وقت کیمرا استعمال کرتے ہوئے تصاویر لے سکتی ہے اور ویڈیوز ریکارڈ کر سکتی ہے۔"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"ایپلیکیشن یا سروس کو سسٹم کے کیمرے تک رسائی حاصل کرنے کی اجازت دیتا ہے تاکہ وہ تصاویر لیں اور ویڈیوز ریکارڈ کریں۔"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"یہ مراعات یافتہ یا سسٹم ایپ کسی بھی وقت ایک سسٹم کیمرا استعمال کرتے ہوئے تصاویر اور ویڈیوز ریکارڈ کر سکتی ہے۔ ایپ کے پاس android.permission.CAMERA کے ليے بھی اجازت ہونا ضروری ہے۔"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ایپلیکیشن یا سروس کو کیمرا کے آلات کے کُھلنے یا بند ہونے سے متعلق کال بیکس موصول کرنے کی اجازت دیں۔"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"والیوم کو تجویز کردہ سطح سے زیادہ کریں؟\n\nزیادہ وقت تک اونچی آواز میں سننے سے آپ کی سماعت کو نقصان پہنچ سکتا ہے۔"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ایکسیسبیلٹی شارٹ کٹ استعمال کریں؟"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"شارٹ کٹ آن ہونے پر، 3 سیکنڈ تک دونوں والیوم بٹنز کو دبانے سے ایک ایکسیسبیلٹی خصوصیت شروع ہو جائے گی۔"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ایکسیسبیلٹی خصوصیات آن کریں؟َ"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ایکسیسبیلٹی خصوصیات کے لیے شارٹ کٹ آن کریں؟"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"دونوں والیوم کی کلیدوں کو کچھ سیکنڈز تک دبائیں رکھنے سے ایکسیسبیلٹی خصوصیات آن ہو جاتی ہیں۔ اس سے آپ کے آلے کے کام کرنے کا طریقہ تبدیل ہو سکتا ہے۔\n\nموجودہ خصوصیات:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nآپ ترتیبات اور ایکسیسبیلٹی میں منتخب کردہ خصوصیات کو تبدیل کر سکتے ہیں۔"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> آن کریں؟"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> شارٹ کٹ آن کریں؟"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"والیوم کی دونوں کلیدوں کو کچھ سیکنڈز تک دبائے رکھنے سے <xliff:g id="SERVICE">%1$s</xliff:g> ایکسیسبیلٹی خصوصیت آن ہو جاتی ہے۔ اس سے آپ کے آلے کے کام کرنے کا طریقہ تبدیل ہو سکتا ہے۔\n\nآپ ترتیبات اور ایکسیسبیلٹی میں دیگر خصوصیت کے لیے اس شارٹ کٹ کو تبدیل کر سکتے ہیں۔"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"آن کریں"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"آن نہ کریں"</string> @@ -1829,8 +1839,7 @@ <item quantity="other">%d گھنٹے کیلئے</item> <item quantity="one">1 گھنٹہ کیلئے</item> </plurals> - <!-- no translation found for zen_mode_until_next_day (1403042784161725038) --> - <skip /> + <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> تک"</string> <string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> تک"</string> <string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> تک (اگلا الارم)"</string> <string name="zen_mode_forever" msgid="740585666364912448">"یہاں تک کہ آپ آف کر دیں"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 5edd9dc8332b..c8ba3c6df1ee 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"audio sozlamalaringizni o‘zgartirish"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Ilovalarga tovush va ovoz chiqarish uchun foydalaniladigan karnay kabi global audio sozlamalarini o‘zgartirish uchun ruxsat beradi."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ovoz yozib olish"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Bu ilova xohlagan vaqtda mikrofon yordami audio yozib olishi mumkin."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"SIM kartaga buyruqlar yuborish"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Dasturga SIM kartaga buyruqlar jo‘natishga ruxsat beradi. Bu juda ham xavfli."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"jismoniy harakatni aniqlash"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Bu ilova jismoniy harakatlaringizni aniqlay oladi."</string> <string name="permlab_camera" msgid="6320282492904119413">"rasm va videoga olish"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Bu ilova xohlagan vaqtda kamera orqali suratga olishi va video yozib olishi mumkin."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Ilova yoki xizmatga tizim kamerasi orqali surat va videolar olishga ruxsat berish"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Bu imtiyozli yoki tizim ilovasi istalgan vaqtda tizim kamerasi orqali surat va videolar olishi mumkin. Ilovada android.permission.CAMERA ruxsati ham yoqilgan boʻlishi talab qilinadi"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Ilova yoki xizmatga kamera qurilmalari ochilayotgani yoki yopilayotgani haqida qayta chaqiruvlar qabul qilishi uchun ruxsat berish."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Tovush balandligi tavsiya etilgan darajadan ham yuqori qilinsinmi?\n\nUzoq vaqt davomida baland ovozda tinglash eshitish qobiliyatingizga salbiy ta’sir ko‘rsatishi mumkin."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Tezkor ishga tushirishdan foydalanilsinmi?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Maxsus imkoniyatlar funksiyasidan foydalanish uchun u yoniqligida ikkala tovush tugmasini 3 soniya bosib turing."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Maxsus imkoniyatlar yoqilsinmi?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Maxsus imkoniyatlar uchun tezkor tugma yoqilsinmi?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Maxsus imkoniyatlarni yoqish uchun ikkala tovush tugmalarini bir necha soniya bosib turing. Qurilmangiz ishlashida oʻzgarish yuz berishi mumkin.\n\nJoriy funksiyalar:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nTanlangan funksiyalarni Sozlamalar ichidagi Maxsus imkoniyatlar ustiga bosib oʻzgartirishingiz mumkin."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> yoqilsinmi?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> tezkor tugmasi yoqilsinmi?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"<xliff:g id="SERVICE">%1$s</xliff:g> funksiyasini yoqish uchun ikkala tovush tugmalarini bir necha soniya bosib turing. Qurilmangiz ishlashida oʻzgarish yuz berishi mumkin.\n\nBu tezkor tugmalarni boshqa funksiyaga Sozlamalar ichidagi Maxsus imkoniyatlar orqali tayinlash mumkin."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Yoqilsin"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Yoqilmasin"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index e3c765872cca..ea83fe8d6337 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"thay đổi cài đặt âm thanh của bạn"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Cho phép ứng dụng sửa đổi cài đặt âm thanh chung chẳng hạn như âm lượng và loa nào được sử dụng cho thiết bị ra."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"ghi âm"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ứng dụng này có thể ghi âm bằng micrô bất kỳ lúc nào."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"gửi lệnh đến SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Cho phép ứng dụng gửi lệnh đến SIM. Việc này rất nguy hiểm."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"nhận dạng hoạt động thể chất"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ứng dụng này có thể nhận dạng hoạt động thể chất của bạn."</string> <string name="permlab_camera" msgid="6320282492904119413">"chụp ảnh và quay video"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Ứng dụng này có thể chụp ảnh và quay video bằng máy ảnh bất cứ lúc nào."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Cho phép một ứng dụng hoặc dịch vụ truy cập vào máy ảnh hệ thống để chụp ảnh và quay video"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ứng dụng hệ thống có đặc quyền này có thể dùng máy ảnh hệ thống để chụp ảnh và quay video bất cứ lúc nào. Ngoài ra, ứng dụng này cũng cần có quyền android.permission.CAMERA"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Cho phép một ứng dụng hoặc dịch vụ nhận lệnh gọi lại khi các thiết bị máy ảnh đang được mở/đóng."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Bạn tăng âm lượng lên quá mức khuyên dùng?\n\nViệc nghe ở mức âm lượng cao trong thời gian dài có thể gây tổn thương thính giác của bạn."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Sử dụng phím tắt Hỗ trợ tiếp cận?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Khi phím tắt này đang bật, thao tác nhấn cả hai nút âm lượng trong 3 giây sẽ mở tính năng hỗ trợ tiếp cận."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Bật các tính năng hỗ trợ tiếp cận?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Bật phím tắt cho các tính năng hỗ trợ tiếp cận?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Thao tác nhấn và giữ cả hai phím âm lượng trong vài giây sẽ bật các tính năng hỗ trợ tiếp cận. Việc bật các tính năng này có thể thay đổi cách thiết bị của bạn hoạt động.\n\nCác tính năng hiện tại:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nBạn có thể thay đổi những tính năng đã chọn trong phần Cài đặt > Hỗ trợ tiếp cận."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Bật <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Bật phím tắt cho <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Thao tác nhấn và giữ cả hai phím âm lượng trong vài giây sẽ bật <xliff:g id="SERVICE">%1$s</xliff:g>, một tính năng hỗ trợ tiếp cận. Việc bật tính năng này có thể thay đổi cách thiết bị của bạn hoạt động.\n\nBạn có thể chuyển phím tắt này thành một tính năng khác trong phần Cài đặt > Hỗ trợ tiếp cận."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Bật"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Không bật"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index dee9d95b3a93..7d3645bdd6a4 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"更改您的音频设置"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"允许该应用修改全局音频设置,例如音量和用于输出的扬声器。"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"录音"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"此应用可随时使用麦克风进行录音。"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"向 SIM 卡发送命令"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"允许应用向SIM卡发送命令(此权限具有很高的危险性)。"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"识别身体活动"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"此应用可以识别您的身体活动。"</string> <string name="permlab_camera" msgid="6320282492904119413">"拍摄照片和视频"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"此应用可随时使用相机拍摄照片和录制视频。"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"要拍照或录制视频,请允许应用或服务访问系统相机"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"这个具有特权的系统应用随时可以使用系统相机拍照及录制视频。另外,应用还需要获取 android.permission.CAMERA 权限"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"允许应用或服务接收与打开或关闭摄像头设备有关的回调。"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要将音量调高到建议的音量以上吗?\n\n长时间保持高音量可能会损伤听力。"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"要使用无障碍快捷方式吗?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"启用这项快捷方式后,同时按下两个音量按钮 3 秒钟即可启动无障碍功能。"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"要开启无障碍功能吗?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"要开启无障碍功能快捷方式吗?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"同时按住两个音量键几秒钟,即可开启无障碍功能。这样做可能会改变您设备的工作方式。\n\n当前功能:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n您可以在“设置”>“无障碍”中更改所选功能。"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"要开启<xliff:g id="SERVICE">%1$s</xliff:g>吗?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"要开启<xliff:g id="SERVICE">%1$s</xliff:g>快捷方式吗?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"同时按住两个音量键几秒钟,即可开启<xliff:g id="SERVICE">%1$s</xliff:g>无障碍功能。这样做可能会改变您设备的工作方式。\n\n您可以在“设置”>“无障碍”中将此快捷方式更改为开启另一项功能。"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"开启"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"不开启"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 44ad8634223a..893f3e9954a2 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"更改音效設定"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"允許應用程式修改全域音頻設定,例如音量和用於輸出的喇叭。"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"錄製音效"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"此應用程式可以隨時使用麥克風錄音。"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"發送指令至 SIM 卡"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"允許應用程式傳送指令到 SIM 卡。這項操作具有高危險性。"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"識別體能活動"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"此應用程式可識別您的體能活動。"</string> <string name="permlab_camera" msgid="6320282492904119413">"拍照和拍攝影片"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"此應用程式可以隨時使用相機拍照和攝錄。"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"允許應用程式或服務存取系統相機來拍照和攝錄"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"這個獲特別權限的系統應用程式可以在任何時候使用系統相機來拍照和攝錄。此外,應用程式亦需要 android.permission.CAMERA 權限"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"允許應用程式或服務接收相機裝置開啟或關閉的相關回電。"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要調高音量 (比建議的音量更大聲) 嗎?\n\n長時間聆聽高分貝音量可能會導致您的聽力受損。"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"要使用無障礙功能快速鍵嗎?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"啟用快速鍵後,同時按住音量按鈕 3 秒便可啟用無障礙功能。"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"要開啟無障礙功能嗎?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"要開啟無障礙功能捷徑嗎?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"同時按下兩個音量鍵幾秒,以開啟無障礙功能。這可能會變更裝置的運作。\n\n目前功能:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n您可在「設定」>「無障礙功能」中變更所選功能。"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"要啟用 <xliff:g id="SERVICE">%1$s</xliff:g> 嗎?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"要開啟 <xliff:g id="SERVICE">%1$s</xliff:g> 捷徑嗎?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"同時按下兩個音量鍵幾秒,以開啟 <xliff:g id="SERVICE">%1$s</xliff:g> 無障礙功能。這可能會變更裝置的運作。\n\n您可在「設定」>「無障礙功能」中變更此快速鍵。"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"開啟"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"不要開啟"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 8831bf0b0815..f92cf4f8e983 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"變更音訊設定"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"允許應用程式修改全域音訊設定,例如音量和用來輸出的喇叭。"</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"錄製音訊"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"這個應用程式隨時可使用麥克風錄音。"</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"傳送指令到 SIM 卡"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"允許應用程式傳送指令到 SIM 卡。這麼做非常危險。"</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"辨識體能活動"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"這個應用程式可以辨識你從事的體能活動。"</string> <string name="permlab_camera" msgid="6320282492904119413">"拍攝相片和影片"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"這個應用程式隨時可使用相機拍照及錄影。"</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"如要拍照或錄影,請允許應用程式或服務存取系統攝影機"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"這個具有特殊權限的系統應用程式隨時可以使用系統攝影機拍照及錄影。此外,你也必須將 android.permission.CAMERA 權限授予這個應用程式"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"允許應用程式或服務接收相機裝置開啟或關閉的相關回呼。"</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要調高音量,比建議的音量更大聲嗎?\n\n長時間聆聽高分貝音量可能會使你的聽力受損。"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"要使用無障礙捷徑嗎?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"啟用捷徑功能,只要同時按下兩個音量按鈕 3 秒,就能啟動無障礙功能。"</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"要開啟無障礙功能嗎?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"要開啟無障礙功能快速鍵嗎?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"同時按住音量調高鍵和調低鍵數秒,即可開啟無障礙功能。這麼做可能會改變裝置的運作方式。\n\n目前的功能:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n你可以在 [設定] > [無障礙設定] 中變更選取的功能。"</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"要開啟「<xliff:g id="SERVICE">%1$s</xliff:g>」嗎?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"要開啟「<xliff:g id="SERVICE">%1$s</xliff:g>」快速鍵嗎?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"同時按住音量調高鍵和調低鍵數秒,即可開啟「<xliff:g id="SERVICE">%1$s</xliff:g>」無障礙功能。這麼做可能會改變裝置的運作方式。\n\n你可以在 [設定] > [無障礙設定] 中變更這個快速鍵觸發的功能。"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"開啟"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"不要開啟"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 5fb9fa61bb36..c43588b5c84a 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -432,13 +432,23 @@ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"shintsha izilungiselelo zakho zomsindo"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Ivumela uhlelo lokusebenza ukushintsha izilungiselelo zomsindo we-global njengevolomu nokuthi isiphi isipika esisetshenziselwa okukhiphayo."</string> <string name="permlab_recordAudio" msgid="1208457423054219147">"qopha umsindo"</string> - <string name="permdesc_recordAudio" msgid="3976213377904701093">"Lolu hlelo lokusebenza lungafunda umsindo lisebenzisa imakrofoni noma kunini."</string> + <!-- no translation found for permdesc_recordAudio (5857246765327514062) --> + <skip /> + <!-- no translation found for permlab_recordBackgroundAudio (5891032812308878254) --> + <skip /> + <!-- no translation found for permdesc_recordBackgroundAudio (1992623135737407516) --> + <skip /> <string name="permlab_sim_communication" msgid="176788115994050692">"thumela imilayezo ku-SIM"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"Ivumela uhlelo lokusebenza ukuthumela imiyalo ku-SIM. Lokhu kuyingozi kakhulu."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"bona umsebenzi"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Lolu hlelo lokusebenza lingabona umsebenzi wakho."</string> <string name="permlab_camera" msgid="6320282492904119413">"thatha izithombe namavidiyo"</string> - <string name="permdesc_camera" msgid="1354600178048761499">"Lolu hlelo lokusebenza lungathatha izithombe futhi lirekhode amavidiyo lusebenzisa ikhamera noma kunini."</string> + <!-- no translation found for permdesc_camera (5240801376168647151) --> + <skip /> + <!-- no translation found for permlab_backgroundCamera (7549917926079731681) --> + <skip /> + <!-- no translation found for permdesc_backgroundCamera (1615291686191138250) --> + <skip /> <string name="permlab_systemCamera" msgid="3642917457796210580">"Vumela uhlelo lokusebenza noma isevisi ukufinyelela kumakhamera wesistimu ukuze uthathe izithombe namavidiyo"</string> <string name="permdesc_systemCamera" msgid="5938360914419175986">"Lolu hlelo lokusebenza oluhle noma lwesistimu lingathatha izithombe futhi lirekhode amavidiyo lisebenzisa ikhamera yesistimu noma kunini. Idinga imvume ye-android.permission.CAMERA ukuthi iphathwe nawuhlelo lokusebenza"</string> <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Vumela uhlelo lokusebenza noma isevisi ukwamukela ukuphinda ufonelwe mayelana namadivayisi wekhamera avuliwe noma avaliwe."</string> @@ -1622,10 +1632,10 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Khuphukisa ivolumu ngaphezu kweleveli enconyiwe?\n\nUkulalela ngevolumu ephezulu izikhathi ezide kungahle kulimaze ukuzwa kwakho."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Sebenzisa isinqamuleli sokufinyelela?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Uma isinqamuleli sivuliwe, ukucindezela zombili izinkinobho zevolumu amasekhondi angu-3 kuzoqalisa isici sokufinyelela."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Uvula izici zokufinyelela?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vula isinqamuleli sezici zokufinyeleleka?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ukubambela phansi bobabili okhiye bevolumu amasekhondi ambalwa kuvula izici zokufinyelela. Lokhu kungashintsha indlela idivayisi yakho esebenza ngayo.\n\nIzici zamanje:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nUngashintsha izici ezikhethiwe Kuzilungiselelo > Ukufinyeleleka."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Vula i-<xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vula isinqamuleli se-<xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ukubambela phansi bobabili okhiye bevolumu amasekhondi ambalwa kuvula i-<xliff:g id="SERVICE">%1$s</xliff:g>, eyisici sokufinyelela Lokhu kungashintsha indlela idivayisi yakho esebenza ngayo.\n\nUngashintshela lesi sinqamuleli kwesinye isici Kuzilungiselelo > Ukufinyeleleka."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Vula"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ungavuli"</string> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 1c71baeaf46a..96ebc127e9ba 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -344,6 +344,11 @@ the app is uninstalled. --> <flag name="immutablyRestricted" value="0x10" /> + <!-- + Modifier for permission restriction. This permission cannot + be exempted by the installer. + --> + <flag name="installerExemptIgnored" value="0x20" /> </attr> <!-- Specified the name of a group that this permission is associated diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 25a9bbd8b445..fc489b1bbe9b 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1166,7 +1166,12 @@ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_recordAudio">record audio</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_recordAudio">This app can record audio using the microphone at any time.</string> + <string name="permdesc_recordAudio">This app can record audio using the microphone while the app is in use.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] --> + <string name="permlab_recordBackgroundAudio">record audio in the background</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] --> + <string name="permdesc_recordBackgroundAudio">This app can record audio using the microphone at any time.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_sim_communication">send commands to the SIM</string> @@ -1181,7 +1186,12 @@ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_camera">take pictures and videos</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_camera">This app can take pictures and record videos using the camera at any time.</string> + <string name="permdesc_camera">This app can take pictures and record videos using the camera while the app is in use.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] --> + <string name="permlab_backgroundCamera">take pictures and videos in the background</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] --> + <string name="permdesc_backgroundCamera">This app can take pictures and record videos using the camera at any time.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> <string name="permlab_systemCamera">Allow an application or service access to system cameras to take pictures and videos</string> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 71cb2acde94e..56f18d5dc1d7 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1599,6 +1599,7 @@ </activity> <activity android:name="android.app.activity.ActivityThreadTest$TestActivity" + android:configChanges="screenLayout|screenSize|orientation|smallestScreenSize" android:supportsPictureInPicture="true" android:exported="true"> </activity> diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java index c9510918e555..7fa16130f25c 100644 --- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java +++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java @@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import android.app.NotificationHistory.HistoricalNotification; import android.graphics.drawable.Icon; import android.os.Parcel; -import android.util.Slog; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -31,6 +30,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; +import java.util.Set; @RunWith(AndroidJUnit4.class) public class NotificationHistoryTest { @@ -297,7 +297,7 @@ public class NotificationHistoryTest { for (int i = 1; i <= 10; i++) { HistoricalNotification n = getHistoricalNotification("pkg", i); - if (i != 2) { + if (i != 2 && i != 4) { postRemoveExpectedStrings.add(n.getPackage()); postRemoveExpectedStrings.add(n.getChannelName()); postRemoveExpectedStrings.add(n.getChannelId()); @@ -318,10 +318,10 @@ public class NotificationHistoryTest { // 1 package name and 20 unique channel names and ids and 5 conversation ids assertThat(history.getPooledStringsToWrite().length).isEqualTo(26); - history.removeConversationFromWrite("pkg", "convo2"); + history.removeConversationsFromWrite("pkg", Set.of("convo2", "convo4")); - // 1 package names and 9 * 2 unique channel names and ids and 4 conversation ids - assertThat(history.getPooledStringsToWrite().length).isEqualTo(23); + // 1 package names and 8 * 2 unique channel names and ids and 3 conversation ids + assertThat(history.getPooledStringsToWrite().length).isEqualTo(20); assertThat(history.getNotificationsToWrite()) .containsExactlyElementsIn(postRemoveExpectedEntries); } diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 23534e2ebebc..8b25afba228e 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -69,6 +69,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; /** * Test for verifying {@link android.app.ActivityThread} class. @@ -78,6 +79,7 @@ import java.util.concurrent.CountDownLatch; @RunWith(AndroidJUnit4.class) @MediumTest public class ActivityThreadTest { + private static final int TIMEOUT_SEC = 10; // The first sequence number to try with. Use a large number to avoid conflicts with the first a // few sequence numbers the framework used to launch the test activity. @@ -309,7 +311,7 @@ public class ActivityThreadTest { transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigPortrait)); appThread.scheduleTransaction(transaction); - activity.mTestLatch.await(); + activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); activity.mConfigLatch.countDown(); activity.mConfigLatch = null; @@ -352,7 +354,7 @@ public class ActivityThreadTest { // Wait until the main thread is performing the configuration change for the configuration // with sequence number BASE_SEQ + 1 before proceeding. This is to mimic the situation where // the activity takes very long time to process configuration changes. - activity.mTestLatch.await(); + activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); config = new Configuration(); config.seq = BASE_SEQ + 2; @@ -738,7 +740,7 @@ public class ActivityThreadTest { mTestLatch.countDown(); } try { - mConfigLatch.await(); + mConfigLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new IllegalStateException(e); } diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java index 0d5025a2b83d..54a281f2a931 100644 --- a/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java +++ b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java @@ -238,7 +238,8 @@ public class AppSearchDocumentTest { .setSchema("schemaType1") .setCreationTimestampMs(5L) .setScore(1) - .setTtlMs(1L); + .setTtlMs(1L) + .setNamespace(""); HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>(); propertyProtoMap.put("longKey1", PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L)); diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java new file mode 100644 index 000000000000..01a25b27baf6 --- /dev/null +++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java @@ -0,0 +1,160 @@ +/* + * 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.app.time; + +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED; +import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; + +import android.os.UserHandle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class TimeZoneCapabilitiesTest { + + private static final UserHandle TEST_USER_HANDLE = UserHandle.of(12345); + + @Test + public void testEquals() { + TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED); + TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED); + { + TimeZoneCapabilities one = builder1.build(); + TimeZoneCapabilities two = builder2.build(); + assertEquals(one, two); + } + + builder2.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED); + { + TimeZoneCapabilities one = builder1.build(); + TimeZoneCapabilities two = builder2.build(); + assertNotEquals(one, two); + } + + builder1.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED); + { + TimeZoneCapabilities one = builder1.build(); + TimeZoneCapabilities two = builder2.build(); + assertEquals(one, two); + } + + builder2.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED); + { + TimeZoneCapabilities one = builder1.build(); + TimeZoneCapabilities two = builder2.build(); + assertNotEquals(one, two); + } + + builder1.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED); + { + TimeZoneCapabilities one = builder1.build(); + TimeZoneCapabilities two = builder2.build(); + assertEquals(one, two); + } + + builder2.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED); + { + TimeZoneCapabilities one = builder1.build(); + TimeZoneCapabilities two = builder2.build(); + assertNotEquals(one, two); + } + + builder1.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED); + { + TimeZoneCapabilities one = builder1.build(); + TimeZoneCapabilities two = builder2.build(); + assertEquals(one, two); + } + } + + @Test + public void testParcelable() { + TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED); + assertRoundTripParcelable(builder.build()); + + builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED); + assertRoundTripParcelable(builder.build()); + + builder.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED); + assertRoundTripParcelable(builder.build()); + + builder.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED); + assertRoundTripParcelable(builder.build()); + } + + @Test + public void testTryApplyConfigChanges_permitted() { + TimeZoneConfiguration oldConfiguration = + new TimeZoneConfiguration.Builder() + .setAutoDetectionEnabled(true) + .setGeoDetectionEnabled(true) + .build(); + TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED) + .build(); + + TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder() + .setAutoDetectionEnabled(false) + .build(); + + TimeZoneConfiguration expected = new TimeZoneConfiguration.Builder(oldConfiguration) + .setAutoDetectionEnabled(false) + .build(); + assertEquals(expected, capabilities.tryApplyConfigChanges(oldConfiguration, configChange)); + } + + @Test + public void testTryApplyConfigChanges_notPermitted() { + TimeZoneConfiguration oldConfiguration = + new TimeZoneConfiguration.Builder() + .setAutoDetectionEnabled(true) + .setGeoDetectionEnabled(true) + .build(); + TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) + .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED) + .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED) + .build(); + + TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder() + .setAutoDetectionEnabled(false) + .build(); + + assertNull(capabilities.tryApplyConfigChanges(oldConfiguration, configChange)); + } +} diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java b/core/tests/coretests/src/android/app/time/TimeZoneConfigurationTest.java index faf908de8d4a..3948eb86c4f6 100644 --- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java +++ b/core/tests/coretests/src/android/app/time/TimeZoneConfigurationTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.app.timezonedetector; +package android.app.time; import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; @@ -23,16 +23,20 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import org.junit.Test; +import org.junit.runner.RunWith; +@RunWith(AndroidJUnit4.class) +@SmallTest public class TimeZoneConfigurationTest { - private static final int ARBITRARY_USER_ID = 9876; - @Test public void testBuilder_copyConstructor() { TimeZoneConfiguration.Builder builder1 = - new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) + new TimeZoneConfiguration.Builder() .setAutoDetectionEnabled(true) .setGeoDetectionEnabled(true); TimeZoneConfiguration configuration1 = builder1.build(); @@ -45,27 +49,27 @@ public class TimeZoneConfigurationTest { @Test public void testIntrospectionMethods() { - TimeZoneConfiguration empty = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID).build(); + TimeZoneConfiguration empty = new TimeZoneConfiguration.Builder().build(); assertFalse(empty.isComplete()); - assertFalse(empty.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED)); + assertFalse(empty.hasIsAutoDetectionEnabled()); - TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) + TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder() .setAutoDetectionEnabled(true) .setGeoDetectionEnabled(true) .build(); assertTrue(completeConfig.isComplete()); - assertTrue(completeConfig.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED)); + assertTrue(completeConfig.hasIsGeoDetectionEnabled()); } @Test public void testBuilder_mergeProperties() { - TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) + TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder() .setAutoDetectionEnabled(true) .build(); { TimeZoneConfiguration mergedEmptyAnd1 = - new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) + new TimeZoneConfiguration.Builder() .mergeProperties(configuration1) .build(); assertEquals(configuration1, mergedEmptyAnd1); @@ -73,7 +77,7 @@ public class TimeZoneConfigurationTest { { TimeZoneConfiguration configuration2 = - new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) + new TimeZoneConfiguration.Builder() .setAutoDetectionEnabled(false) .build(); @@ -90,22 +94,14 @@ public class TimeZoneConfigurationTest { @Test public void testEquals() { TimeZoneConfiguration.Builder builder1 = - new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID); + new TimeZoneConfiguration.Builder(); { TimeZoneConfiguration one = builder1.build(); assertEquals(one, one); } - { - TimeZoneConfiguration.Builder differentUserBuilder = - new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID + 1); - TimeZoneConfiguration one = builder1.build(); - TimeZoneConfiguration two = differentUserBuilder.build(); - assertNotEquals(one, two); - } - TimeZoneConfiguration.Builder builder2 = - new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID); + new TimeZoneConfiguration.Builder(); { TimeZoneConfiguration one = builder1.build(); TimeZoneConfiguration two = builder2.build(); @@ -159,7 +155,7 @@ public class TimeZoneConfigurationTest { @Test public void testParcelable() { TimeZoneConfiguration.Builder builder = - new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID); + new TimeZoneConfiguration.Builder(); assertRoundTripParcelable(builder.build()); builder.setAutoDetectionEnabled(true); diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java deleted file mode 100644 index db127c6cb9ed..000000000000 --- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java +++ /dev/null @@ -1,186 +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.app.timezonedetector; - -import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; -import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED; -import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; - -import org.junit.Test; - -public class TimeZoneCapabilitiesTest { - - private static final int ARBITRARY_USER_ID = 12345; - - @Test - public void testEquals() { - TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) - .setAutoDetectionEnabled(true) - .setGeoDetectionEnabled(true) - .build(); - TimeZoneConfiguration configuration2 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) - .setAutoDetectionEnabled(false) - .setGeoDetectionEnabled(false) - .build(); - - TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder() - .setConfiguration(configuration1) - .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED) - .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED) - .setSuggestManualTimeZone(CAPABILITY_POSSESSED); - TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder() - .setConfiguration(configuration1) - .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED) - .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED) - .setSuggestManualTimeZone(CAPABILITY_POSSESSED); - { - TimeZoneCapabilities one = builder1.build(); - TimeZoneCapabilities two = builder2.build(); - assertEquals(one, two); - } - - builder2.setConfiguration(configuration2); - { - TimeZoneCapabilities one = builder1.build(); - TimeZoneCapabilities two = builder2.build(); - assertNotEquals(one, two); - } - - builder1.setConfiguration(configuration2); - { - TimeZoneCapabilities one = builder1.build(); - TimeZoneCapabilities two = builder2.build(); - assertEquals(one, two); - } - - builder2.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED); - { - TimeZoneCapabilities one = builder1.build(); - TimeZoneCapabilities two = builder2.build(); - assertNotEquals(one, two); - } - - builder1.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED); - { - TimeZoneCapabilities one = builder1.build(); - TimeZoneCapabilities two = builder2.build(); - assertEquals(one, two); - } - - builder2.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED); - { - TimeZoneCapabilities one = builder1.build(); - TimeZoneCapabilities two = builder2.build(); - assertNotEquals(one, two); - } - - builder1.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED); - { - TimeZoneCapabilities one = builder1.build(); - TimeZoneCapabilities two = builder2.build(); - assertEquals(one, two); - } - - builder2.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED); - { - TimeZoneCapabilities one = builder1.build(); - TimeZoneCapabilities two = builder2.build(); - assertNotEquals(one, two); - } - - builder1.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED); - { - TimeZoneCapabilities one = builder1.build(); - TimeZoneCapabilities two = builder2.build(); - assertEquals(one, two); - } - } - - @Test - public void testParcelable() { - TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) - .setAutoDetectionEnabled(true) - .setGeoDetectionEnabled(true) - .build(); - TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder() - .setConfiguration(configuration) - .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED) - .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED) - .setSuggestManualTimeZone(CAPABILITY_POSSESSED); - assertRoundTripParcelable(builder.build()); - - builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED); - assertRoundTripParcelable(builder.build()); - - builder.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED); - assertRoundTripParcelable(builder.build()); - - builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED); - assertRoundTripParcelable(builder.build()); - } - - @Test - public void testApplyUpdate_permitted() { - TimeZoneConfiguration oldConfiguration = - new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) - .setAutoDetectionEnabled(true) - .setGeoDetectionEnabled(true) - .build(); - TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder() - .setConfiguration(oldConfiguration) - .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED) - .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED) - .setSuggestManualTimeZone(CAPABILITY_POSSESSED) - .build(); - assertEquals(oldConfiguration, capabilities.getConfiguration()); - - TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) - .setAutoDetectionEnabled(false) - .build(); - - TimeZoneConfiguration expected = new TimeZoneConfiguration.Builder(oldConfiguration) - .setAutoDetectionEnabled(false) - .build(); - assertEquals(expected, capabilities.applyUpdate(configChange)); - } - - @Test - public void testApplyUpdate_notPermitted() { - TimeZoneConfiguration oldConfiguration = - new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) - .setAutoDetectionEnabled(true) - .setGeoDetectionEnabled(true) - .build(); - TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder() - .setConfiguration(oldConfiguration) - .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED) - .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED) - .setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED) - .build(); - assertEquals(oldConfiguration, capabilities.getConfiguration()); - - TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) - .setAutoDetectionEnabled(false) - .build(); - - assertNull(capabilities.applyUpdate(configChange)); - } -} diff --git a/core/tests/coretests/src/android/view/InsetsFlagsTest.java b/core/tests/coretests/src/android/view/InsetsFlagsTest.java deleted file mode 100644 index b4302e79ed6a..000000000000 --- a/core/tests/coretests/src/android/view/InsetsFlagsTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - - -import static android.view.InsetsFlags.getAppearance; -import static android.view.View.NAVIGATION_BAR_TRANSLUCENT; -import static android.view.View.NAVIGATION_BAR_TRANSPARENT; -import static android.view.View.STATUS_BAR_TRANSLUCENT; -import static android.view.View.STATUS_BAR_TRANSPARENT; -import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; -import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; -import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE; -import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; -import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; -import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; -import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS; -import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS; - -import static org.junit.Assert.assertTrue; - -import android.platform.test.annotations.Presubmit; -import android.view.WindowInsetsController.Appearance; - -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for {@link InsetsFlags}. - * - * <p>Build/Install/Run: - * atest FrameworksCoreTests:InsetsFlagsTest - * - * <p>This test class is a part of Window Manager Service tests and specified in - * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}. - */ -@Presubmit -@RunWith(AndroidJUnit4.class) -public class InsetsFlagsTest { - - @Test - public void testGetAppearance() { - assertContainsAppearance(APPEARANCE_LOW_PROFILE_BARS, SYSTEM_UI_FLAG_LOW_PROFILE); - assertContainsAppearance(APPEARANCE_LIGHT_STATUS_BARS, SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); - assertContainsAppearance(APPEARANCE_LIGHT_NAVIGATION_BARS, - SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); - assertContainsAppearance(APPEARANCE_OPAQUE_STATUS_BARS, - 0xffffffff & ~(STATUS_BAR_TRANSLUCENT | STATUS_BAR_TRANSPARENT)); - assertContainsAppearance(APPEARANCE_OPAQUE_NAVIGATION_BARS, - 0xffffffff & ~(NAVIGATION_BAR_TRANSLUCENT | NAVIGATION_BAR_TRANSPARENT)); - } - - void assertContainsAppearance(@Appearance int appearance, int systemUiVisibility) { - assertTrue((getAppearance(systemUiVisibility) & appearance) == appearance); - } -} diff --git a/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java b/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java index ec8dfcbfbde9..91266f0bbb9e 100644 --- a/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java +++ b/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java @@ -16,20 +16,10 @@ package android.widget; -import static android.widget.RichContentReceiver.SOURCE_PROCESS_TEXT; - import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import android.app.Activity; import android.app.Instrumentation; -import android.content.ClipData; -import android.content.ClipDescription; import android.content.Intent; import android.text.Selection; import android.text.Spannable; @@ -44,10 +34,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentMatcher; -import org.mockito.Mockito; - -import java.util.Set; /** * Tests for {@link Intent#ACTION_PROCESS_TEXT} functionality in {@link TextView}. @@ -78,9 +64,6 @@ public class TextViewProcessTextTest { mTextView.setTextIsSelectable(true); Selection.setSelection((Spannable) mTextView.getText(), 0, mTextView.getText().length()); - MockReceiverWrapper mockReceiverWrapper = new MockReceiverWrapper(); - mTextView.setRichContentReceiver(mockReceiverWrapper); - // We need to run this in the UI thread, as it will create a Toast. mActivityRule.runOnUiThread(() -> { triggerOnActivityResult(Activity.RESULT_OK, "Text is replaced."); @@ -89,46 +72,6 @@ public class TextViewProcessTextTest { // This is a TextView, which can't be modified. Hence no change should have been made. assertEquals(originalText, mTextView.getText().toString()); - verifyZeroInteractions(mockReceiverWrapper.mMock); - } - - @Test - public void testProcessTextActivityResultEditable_defaultRichContentReceiver() - throws Throwable { - mActivityRule.runOnUiThread(() -> mTextView = new EditText(mActivity)); - mInstrumentation.waitForIdleSync(); - CharSequence originalText = "This is some text."; - mTextView.setText(originalText, BufferType.SPANNABLE); - assertEquals(originalText, mTextView.getText().toString()); - mTextView.setTextIsSelectable(true); - Selection.setSelection(((EditText) mTextView).getText(), 0, mTextView.getText().length()); - - CharSequence newText = "Text is replaced."; - triggerOnActivityResult(Activity.RESULT_OK, newText); - - assertEquals(newText, mTextView.getText().toString()); - } - - @Test - public void testProcessTextActivityResultEditable_customRichContentReceiver() throws Throwable { - mActivityRule.runOnUiThread(() -> mTextView = new EditText(mActivity)); - mInstrumentation.waitForIdleSync(); - CharSequence originalText = "This is some text."; - mTextView.setText(originalText, BufferType.SPANNABLE); - assertEquals(originalText, mTextView.getText().toString()); - mTextView.setTextIsSelectable(true); - Selection.setSelection(((EditText) mTextView).getText(), 0, mTextView.getText().length()); - - MockReceiverWrapper mockReceiverWrapper = new MockReceiverWrapper(); - mTextView.setRichContentReceiver(mockReceiverWrapper); - - CharSequence newText = "Text is replaced."; - triggerOnActivityResult(Activity.RESULT_OK, newText); - - ClipData expectedClip = ClipData.newPlainText("", newText); - verify(mockReceiverWrapper.mMock, times(1)).onReceive( - eq(mTextView), clipEq(expectedClip), eq(SOURCE_PROCESS_TEXT), eq(0)); - verifyNoMoreInteractions(mockReceiverWrapper.mMock); } @Test @@ -141,14 +84,10 @@ public class TextViewProcessTextTest { mTextView.setTextIsSelectable(true); Selection.setSelection(((EditText) mTextView).getText(), 0, mTextView.getText().length()); - MockReceiverWrapper mockReceiverWrapper = new MockReceiverWrapper(); - mTextView.setRichContentReceiver(mockReceiverWrapper); - CharSequence newText = "Text is replaced."; triggerOnActivityResult(Activity.RESULT_CANCELED, newText); assertEquals(originalText, mTextView.getText().toString()); - verifyZeroInteractions(mockReceiverWrapper.mMock); } @Test @@ -161,13 +100,9 @@ public class TextViewProcessTextTest { mTextView.setTextIsSelectable(true); Selection.setSelection(((EditText) mTextView).getText(), 0, mTextView.getText().length()); - MockReceiverWrapper mockReceiverWrapper = new MockReceiverWrapper(); - mTextView.setRichContentReceiver(mockReceiverWrapper); - mTextView.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, Activity.RESULT_OK, null); assertEquals(originalText, mTextView.getText().toString()); - verifyZeroInteractions(mockReceiverWrapper.mMock); } private void triggerOnActivityResult(int resultCode, CharSequence replacementText) { @@ -175,55 +110,4 @@ public class TextViewProcessTextTest { data.putExtra(Intent.EXTRA_PROCESS_TEXT, replacementText); mTextView.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, resultCode, data); } - - // This wrapper is used so that we only mock and verify the public callback methods. In addition - // to the public methods, the RichContentReceiver interface has some hidden default methods; - // we don't want to mock or assert calls to these helper functions (they are an implementation - // detail). - private static class MockReceiverWrapper implements RichContentReceiver<TextView> { - private final RichContentReceiver<TextView> mMock; - - @SuppressWarnings("unchecked") - MockReceiverWrapper() { - this.mMock = Mockito.mock(RichContentReceiver.class); - } - - public RichContentReceiver<TextView> getMock() { - return mMock; - } - - @Override - public boolean onReceive(TextView view, ClipData clip, @Source int source, - @Flags int flags) { - return mMock.onReceive(view, clip, source, flags); - } - - @Override - public Set<String> getSupportedMimeTypes() { - return mMock.getSupportedMimeTypes(); - } - } - - private static ClipData clipEq(ClipData expected) { - return argThat(new ClipDataArgumentMatcher(expected)); - } - - private static class ClipDataArgumentMatcher implements ArgumentMatcher<ClipData> { - private final ClipData mExpected; - - private ClipDataArgumentMatcher(ClipData expected) { - this.mExpected = expected; - } - - @Override - public boolean matches(ClipData actual) { - ClipDescription actualDesc = actual.getDescription(); - ClipDescription expectedDesc = mExpected.getDescription(); - return expectedDesc.getLabel().equals(actualDesc.getLabel()) - && actualDesc.getMimeTypeCount() == 1 - && expectedDesc.getMimeType(0).equals(actualDesc.getMimeType(0)) - && actual.getItemCount() == 1 - && mExpected.getItemAt(0).getText().equals(actual.getItemAt(0).getText()); - } - } } diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java new file mode 100644 index 000000000000..403c1c219a52 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java @@ -0,0 +1,152 @@ +/* + * 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 static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.os.FileUtils; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class KernelSingleProcessCpuThreadReaderTest { + + private File mProcDirectory; + + @Before + public void setUp() { + Context context = InstrumentationRegistry.getContext(); + mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE); + } + + @After + public void tearDown() throws Exception { + FileUtils.deleteContents(mProcDirectory); + } + + @Test + public void getThreadCpuUsage() throws IOException { + setupDirectory(42, + new int[] {42, 1, 2, 3}, + new int[] {1000, 2000}, + // Units are 10ms aka 10000Us + new int[][] {{100, 200}, {0, 200}, {100, 300}, {0, 400}}, + new int[] {1400, 1500}); + + KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(42, + mProcDirectory.toPath()); + KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage = + reader.getProcessCpuUsage(); + assertThat(processCpuUsage.cpuTimeMillis).isEqualTo(29000); + List<KernelSingleProcessCpuThreadReader.ThreadCpuUsage> threadCpuUsage = + processCpuUsage.threadCpuUsages; + threadCpuUsage.sort(Comparator.comparingInt(o -> o.threadId)); + assertThat(threadCpuUsage).hasSize(4); + assertThat(threadCpuUsage.get(0).threadId).isEqualTo(1); + assertThat(threadCpuUsage.get(0).usageTimesMillis).isEqualTo(new long[]{0, 2000}); + assertThat(threadCpuUsage.get(1).threadId).isEqualTo(2); + assertThat(threadCpuUsage.get(1).usageTimesMillis).isEqualTo(new long[]{1000, 3000}); + assertThat(threadCpuUsage.get(2).threadId).isEqualTo(3); + assertThat(threadCpuUsage.get(2).usageTimesMillis).isEqualTo(new long[]{0, 4000}); + assertThat(threadCpuUsage.get(3).threadId).isEqualTo(42); + assertThat(threadCpuUsage.get(3).usageTimesMillis).isEqualTo(new long[]{1000, 2000}); + } + + @Test + public void getCpuFrequencyCount() throws IOException { + setupDirectory(13, + new int[] {13}, + new int[] {1000, 2000, 3000}, + new int[][] {{100, 200, 300}}, + new int[] {14, 15}); + + KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(13, + mProcDirectory.toPath()); + int cpuFrequencyCount = reader.getCpuFrequencyCount(); + assertThat(cpuFrequencyCount).isEqualTo(3); + } + + private void setupDirectory(int pid, int[] threadIds, int[] cpuFrequencies, + int[][] threadCpuTimes, int[] processCpuTimes) + throws IOException { + + assertTrue(mProcDirectory.toPath().resolve("self").toFile().mkdirs()); + + try (OutputStream timeInStateStream = + Files.newOutputStream( + mProcDirectory.toPath().resolve("self").resolve("time_in_state"))) { + for (int i = 0; i < cpuFrequencies.length; i++) { + final String line = cpuFrequencies[i] + " 0\n"; + timeInStateStream.write(line.getBytes()); + } + } + + Path processPath = mProcDirectory.toPath().resolve(String.valueOf(pid)); + + // Make /proc/$PID + assertTrue(processPath.toFile().mkdirs()); + + // Write /proc/$PID/stat. Only the fields 14-17 matter. + try (OutputStream timeInStateStream = Files.newOutputStream(processPath.resolve("stat"))) { + timeInStateStream.write( + (pid + " (test) S 4 5 6 7 8 9 10 11 12 13 " + + processCpuTimes[0] + " " + + processCpuTimes[1] + " " + + "16 17 18 19 20 ...").getBytes()); + } + + // Make /proc/$PID/task + final Path selfThreadsPath = processPath.resolve("task"); + assertTrue(selfThreadsPath.toFile().mkdirs()); + + // Make thread directories + for (int i = 0; i < threadIds.length; i++) { + // Make /proc/$PID/task/$TID + final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i])); + assertTrue(threadPath.toFile().mkdirs()); + + // Make /proc/$PID/task/$TID/time_in_state + try (OutputStream timeInStateStream = + Files.newOutputStream(threadPath.resolve("time_in_state"))) { + for (int j = 0; j < cpuFrequencies.length; j++) { + final String line = cpuFrequencies[j] + " " + threadCpuTimes[i][j] + "\n"; + timeInStateStream.write(line.getBytes()); + } + } + } + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java index 10ba54865dbe..121c637310c6 100644 --- a/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java @@ -55,82 +55,107 @@ public class SystemServerCpuThreadReaderTest { @Test public void testReaderDelta_firstTime() throws IOException { - int uid = 42; + int pid = 42; setupDirectory( - mProcDirectory.toPath().resolve(String.valueOf(uid)), - new int[]{42, 1, 2, 3}, - new int[]{1000, 2000}, + pid, + new int[] {42, 1, 2, 3}, + new int[] {1000, 2000}, // Units are 10ms aka 10000Us - new int[][]{{100, 200}, {0, 200}, {0, 300}, {0, 400}}); + new int[][] {{100, 200}, {0, 200}, {0, 300}, {0, 400}}, + new int[] {1400, 1500}); SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader( - mProcDirectory.toPath(), uid); - reader.setBinderThreadNativeTids(new int[]{1, 3}); + mProcDirectory.toPath(), pid); + reader.setBinderThreadNativeTids(new int[] {1, 3}); SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes = reader.readDelta(); - assertArrayEquals(new long[]{100 * 10000, 1100 * 10000}, + assertArrayEquals(new long[] {100 * 10000, 1100 * 10000}, systemServiceCpuThreadTimes.threadCpuTimesUs); - assertArrayEquals(new long[]{0, 600 * 10000}, + assertArrayEquals(new long[] {0, 600 * 10000}, systemServiceCpuThreadTimes.binderThreadCpuTimesUs); } @Test public void testReaderDelta_nextTime() throws IOException { - int uid = 42; + int pid = 42; setupDirectory( - mProcDirectory.toPath().resolve(String.valueOf(uid)), - new int[]{42, 1, 2, 3}, - new int[]{1000, 2000}, - new int[][]{{100, 200}, {0, 200}, {0, 300}, {0, 400}}); + pid, + new int[] {42, 1, 2, 3}, + new int[] {1000, 2000}, + new int[][] {{100, 200}, {0, 200}, {0, 300}, {0, 400}}, + new int[] {1400, 1500}); SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader( - mProcDirectory.toPath(), uid); - reader.setBinderThreadNativeTids(new int[]{1, 3}); + mProcDirectory.toPath(), pid); + reader.setBinderThreadNativeTids(new int[] {1, 3}); // First time, populate "last" snapshot reader.readDelta(); FileUtils.deleteContents(mProcDirectory); setupDirectory( - mProcDirectory.toPath().resolve(String.valueOf(uid)), - new int[]{42, 1, 2, 3}, - new int[]{1000, 2000}, - new int[][]{{500, 600}, {700, 800}, {900, 1000}, {1100, 1200}}); + pid, + new int[] {42, 1, 2, 3}, + new int[] {1000, 2000}, + new int[][] {{500, 600}, {700, 800}, {900, 1000}, {1100, 1200}}, + new int[] {2400, 2500}); // Second time, get the actual delta SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes = reader.readDelta(); - assertArrayEquals(new long[]{3100 * 10000, 2500 * 10000}, + assertArrayEquals(new long[] {3100 * 10000, 2500 * 10000}, systemServiceCpuThreadTimes.threadCpuTimesUs); - assertArrayEquals(new long[]{1800 * 10000, 1400 * 10000}, + assertArrayEquals(new long[] {1800 * 10000, 1400 * 10000}, systemServiceCpuThreadTimes.binderThreadCpuTimesUs); } - private void setupDirectory(Path processPath, int[] threadIds, int[] cpuFrequencies, - int[][] cpuTimes) throws IOException { + private void setupDirectory(int pid, int[] threadIds, int[] cpuFrequencies, int[][] cpuTimes, + int[] processCpuTimes) + throws IOException { + + assertTrue(mProcDirectory.toPath().resolve("self").toFile().mkdirs()); + + try (OutputStream timeInStateStream = + Files.newOutputStream( + mProcDirectory.toPath().resolve("self").resolve("time_in_state"))) { + for (int i = 0; i < cpuFrequencies.length; i++) { + final String line = cpuFrequencies[i] + " 0\n"; + timeInStateStream.write(line.getBytes()); + } + } + + Path processPath = mProcDirectory.toPath().resolve(String.valueOf(pid)); // Make /proc/$PID assertTrue(processPath.toFile().mkdirs()); + // Write /proc/$PID/stat. Only the fields 14-17 matter. + try (OutputStream timeInStateStream = Files.newOutputStream(processPath.resolve("stat"))) { + timeInStateStream.write( + (pid + " (test) S 4 5 6 7 8 9 10 11 12 13 " + + processCpuTimes[0] + " " + + processCpuTimes[1] + " " + + "16 17 18 19 20 ...").getBytes()); + } + // Make /proc/$PID/task final Path selfThreadsPath = processPath.resolve("task"); assertTrue(selfThreadsPath.toFile().mkdirs()); - // Make thread directories in reverse order, as they are read in order of creation by - // CpuThreadProcReader + // Make thread directories for (int i = 0; i < threadIds.length; i++) { // Make /proc/$PID/task/$TID final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i])); assertTrue(threadPath.toFile().mkdirs()); // Make /proc/$PID/task/$TID/time_in_state - final OutputStream timeInStateStream = - Files.newOutputStream(threadPath.resolve("time_in_state")); - for (int j = 0; j < cpuFrequencies.length; j++) { - final String line = cpuFrequencies[j] + " " + cpuTimes[i][j] + "\n"; - timeInStateStream.write(line.getBytes()); + try (OutputStream timeInStateStream = + Files.newOutputStream(threadPath.resolve("time_in_state"))) { + for (int j = 0; j < cpuFrequencies.length; j++) { + final String line = cpuFrequencies[j] + " " + cpuTimes[i][j] + "\n"; + timeInStateStream.write(line.getBytes()); + } } - timeInStateStream.close(); } } } 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 2eee140b921f..37e1efdecfd1 100644 --- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java @@ -43,18 +43,18 @@ public class SystemServicePowerCalculatorTest { private PowerProfile mProfile; private MockBatteryStatsImpl mMockBatteryStats; private MockKernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader; - private MockServerCpuThreadReader mMockServerCpuThreadReader; + private MockSystemServerCpuThreadReader mMockSystemServerCpuThreadReader; private SystemServicePowerCalculator mSystemServicePowerCalculator; @Before public void setUp() throws IOException { Context context = InstrumentationRegistry.getContext(); mProfile = new PowerProfile(context, true /* forTest */); - mMockServerCpuThreadReader = new MockServerCpuThreadReader(); + mMockSystemServerCpuThreadReader = new MockSystemServerCpuThreadReader(); mMockCpuUidFreqTimeReader = new MockKernelCpuUidFreqTimeReader(); mMockBatteryStats = new MockBatteryStatsImpl(new MockClocks()) .setPowerProfile(mProfile) - .setSystemServerCpuThreadReader(mMockServerCpuThreadReader) + .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader) .setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader) .setUserInfoProvider(new MockUserInfoProvider()); mMockBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0); @@ -65,12 +65,13 @@ public class SystemServicePowerCalculatorTest { @Test public void testCalculateApp() { // Test Power Profile has two CPU clusters with 3 and 4 speeds, thus 7 freq times total - mMockServerCpuThreadReader.setThreadTimes( - new long[]{30000, 40000, 50000, 60000, 70000, 80000, 90000}, - new long[]{20000, 30000, 40000, 50000, 60000, 70000, 80000}); + mMockSystemServerCpuThreadReader.setCpuTimes( + 210000, + new long[] {30000, 40000, 50000, 60000, 70000, 80000, 90000}, + new long[] {20000, 30000, 40000, 50000, 60000, 70000, 80000}); mMockCpuUidFreqTimeReader.setSystemServerCpuTimes( - new long[]{10000, 20000, 30000, 40000, 50000, 60000, 70000} + new long[] {10000, 20000, 30000, 40000, 50000, 60000, 70000} ); mMockBatteryStats.readKernelUidCpuFreqTimesLocked(null, true, false); @@ -106,13 +107,13 @@ public class SystemServicePowerCalculatorTest { mMockBatteryStats.getUidStatsLocked(workSourceUid1), 0); mSystemServicePowerCalculator.calculateApp(app1, app1.uidObj, 0, 0, BatteryStats.STATS_SINCE_CHARGED); - assertEquals(0.27162, app1.systemServiceCpuPowerMah, 0.00001); + assertEquals(0.00018958, app1.systemServiceCpuPowerMah, 0.0000001); BatterySipper app2 = new BatterySipper(BatterySipper.DrainType.APP, mMockBatteryStats.getUidStatsLocked(workSourceUid2), 0); mSystemServicePowerCalculator.calculateApp(app2, app2.uidObj, 0, 0, BatteryStats.STATS_SINCE_CHARGED); - assertEquals(2.44458, app2.systemServiceCpuPowerMah, 0.00001); + assertEquals(0.00170625, app2.systemServiceCpuPowerMah, 0.0000001); } private static class MockKernelCpuUidFreqTimeReader extends @@ -140,14 +141,16 @@ public class SystemServicePowerCalculatorTest { } } - private static class MockServerCpuThreadReader extends SystemServerCpuThreadReader { + private static class MockSystemServerCpuThreadReader extends SystemServerCpuThreadReader { private SystemServiceCpuThreadTimes mThreadTimes = new SystemServiceCpuThreadTimes(); - MockServerCpuThreadReader() { + MockSystemServerCpuThreadReader() { super(null); } - public void setThreadTimes(long[] threadCpuTimesUs, long[] binderThreadCpuTimesUs) { + public void setCpuTimes(long processCpuTimeUs, long[] threadCpuTimesUs, + long[] binderThreadCpuTimesUs) { + mThreadTimes.processCpuTimeUs = processCpuTimeUs; mThreadTimes.threadCpuTimesUs = threadCpuTimesUs; mThreadTimes.binderThreadCpuTimesUs = binderThreadCpuTimesUs; } diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 10c2b096ef98..977703dcd956 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -153,6 +153,7 @@ <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" /> <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" /> <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="media" /> + <assign-permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" uid="media" /> <assign-permission name="android.permission.INTERNET" uid="media" /> @@ -222,6 +223,15 @@ targetSdk="29"> <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" /> </split-permission> + <split-permission name="android.permission.RECORD_AUDIO" + targetSdk="31"> + <new-permission name="android.permission.RECORD_BACKGROUND_AUDIO" /> + </split-permission> + <split-permission name="android.permission.CAMERA" + targetSdk="31"> + <new-permission name="android.permission.BACKGROUND_CAMERA" /> + </split-permission> + <!-- This is a list of all the libraries available for application code to link against. --> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 2779a75378b7..81da5c865f50 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -435,6 +435,8 @@ applications that come with the platform <permission name="android.permission.CAPTURE_AUDIO_OUTPUT" /> <!-- Permissions required for CTS test - AdbManagerTest --> <permission name="android.permission.MANAGE_DEBUGGING" /> + <!-- Permissions required for CTS test - TimeManagerTest --> + <permission name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 86e7adf945b7..c53ea8789c8b 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -205,6 +205,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-1844540996": { + "message": " Initial targets: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1838803135": { "message": "Attempted to set windowing mode to a display that does not exist: %d", "level": "WARN", @@ -343,12 +349,24 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "-1587921395": { + "message": " Top targets: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1568331821": { "message": "Enabling listeners", "level": "VERBOSE", "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DisplayRotation.java" }, + "-1567866547": { + "message": "Collecting in transition %d: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1554521902": { "message": "showInsets(ime) was requested by different window: %s ", "level": "WARN", @@ -421,6 +439,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-1452274694": { + "message": " CAN PROMOTE: promoting to parent %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1443029505": { "message": "SAFE MODE ENABLED (menu=%d s=%d dpad=%d trackball=%d)", "level": "INFO", @@ -475,6 +499,12 @@ "group": "WM_DEBUG_SYNC_ENGINE", "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" }, + "-1375751630": { + "message": " --- Start combine pass ---", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1364754753": { "message": "Task vanished taskId=%d", "level": "VERBOSE", @@ -787,6 +817,12 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-855366859": { + "message": " merging children in from %s: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-853404763": { "message": "\twallpaper=%s", "level": "DEBUG", @@ -871,6 +907,12 @@ "group": "WM_DEBUG_FOCUS", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-703543418": { + "message": " check sibling %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-694710814": { "message": "Pausing rotation during drag", "level": "DEBUG", @@ -925,6 +967,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-622017164": { + "message": "Finish Transition: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "-618015844": { "message": "performEnableScreen: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b mOnlyCore=%b. %s", "level": "INFO", @@ -979,12 +1027,24 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowAnimator.java" }, + "-532081937": { + "message": " Commit activity becoming invisible: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-519504830": { "message": "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s isEntrance=%b Callers=%s", "level": "VERBOSE", "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/AppTransition.java" }, + "-509601642": { + "message": " checking %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-507657818": { "message": "Window %s is already added", "level": "WARN", @@ -1027,6 +1087,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" }, + "-446752714": { + "message": " SKIP: sibling contains top target %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-445944810": { "message": "finish(%b): mCanceled=%b", "level": "DEBUG", @@ -1153,6 +1219,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-302335479": { + "message": " remove from topTargets %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-272719931": { "message": "startLockTaskModeLocked: %s", "level": "WARN", @@ -1441,12 +1513,24 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java" }, + "182319432": { + "message": " remove from targets %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "184362060": { "message": "screenshotTask(%d): mCanceled=%b", "level": "DEBUG", "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimationController.java" }, + "184610856": { + "message": "Start calculating TransitionInfo based on participants: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "186668272": { "message": "Now changing app %s", "level": "VERBOSE", @@ -1525,6 +1609,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, + "259206414": { + "message": "Creating Transition: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "269576220": { "message": "Resuming rotation after drag", "level": "DEBUG", @@ -1657,6 +1747,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "430260320": { + "message": " sibling is a top target with mode %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "435494046": { "message": "Attempted to add window to a display for which the application does not have access: %d. Aborting.", "level": "WARN", @@ -1711,6 +1807,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/AppTransition.java" }, + "528150092": { + "message": " keep as target %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "531242746": { "message": " THUMBNAIL %s: CREATE", "level": "INFO", @@ -1801,6 +1903,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "625447638": { + "message": "Resize reasons for w=%s: %s configChanged=%b dragResizingChanged=%b reportOrientationChanged=%b", + "level": "VERBOSE", + "group": "WM_DEBUG_RESIZE", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "628276090": { "message": "Delaying app transition for screen rotation animation to finish", "level": "VERBOSE", @@ -1897,6 +2005,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "744171317": { + "message": " SKIP: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "745391677": { "message": " CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x \/ %s", "level": "INFO", @@ -1915,6 +2029,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/TaskPositioner.java" }, + "793568608": { + "message": " SKIP: sibling is visible but not part of transition", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "794570322": { "message": "Now closing app %s", "level": "VERBOSE", @@ -1951,6 +2071,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "849147756": { + "message": "Finish collecting in transition %d", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "853091290": { "message": "Moved stack=%s behind stack=%s", "level": "DEBUG", @@ -2035,6 +2161,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "996960396": { + "message": "Starting Transition %d", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "1000601037": { "message": "SyncSet{%x:%d} Set ready", "level": "VERBOSE", @@ -2095,6 +2227,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "1115248873": { + "message": "Calling onTransitionReady: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "1115417974": { "message": "FORCED DISPLAY SIZE: %dx%d", "level": "INFO", @@ -2113,18 +2251,18 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java" }, - "1160771501": { - "message": "Resize reasons for w=%s: %s surfaceResized=%b configChanged=%b dragResizingChanged=%b reportOrientationChanged=%b", - "level": "VERBOSE", - "group": "WM_DEBUG_RESIZE", - "at": "com\/android\/server\/wm\/WindowState.java" - }, "1166381079": { "message": "Execute app transition: %s, displayId: %d Callers=%s", "level": "WARN", "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "1186730970": { + "message": " no common mode yet, so set it", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "1208313423": { "message": "addWindowToken: Attempted to add token: %s for non-exiting displayId=%d", "level": "WARN", @@ -2293,6 +2431,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, + "1469310004": { + "message": " SKIP: common mode mismatch. was %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "1495525537": { "message": "createWallpaperAnimations()", "level": "DEBUG", @@ -2509,6 +2653,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1794249572": { + "message": "Requesting StartTransition: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "1822843721": { "message": "Aborted starting %s: startingData=%s", "level": "VERBOSE", @@ -2826,6 +2976,9 @@ "WM_DEBUG_WINDOW_ORGANIZER": { "tag": "WindowManager" }, + "WM_DEBUG_WINDOW_TRANSITIONS": { + "tag": "WindowManager" + }, "WM_ERROR": { "tag": "WindowManager" }, diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index 0504cdc15b52..bda84dd76cf5 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -283,10 +283,30 @@ <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font> </family> <family lang="und-Ethi"> - <font weight="400" style="normal">NotoSansEthiopic-Regular.ttf</font> - <font weight="700" style="normal">NotoSansEthiopic-Bold.ttf</font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifEthiopic-Regular.otf</font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifEthiopic-Bold.otf</font> + <font weight="400" style="normal">NotoSansEthiopic-VF.ttf + <axis tag="wght" stylevalue="400" /> + </font> + <font weight="500" style="normal">NotoSansEthiopic-VF.ttf + <axis tag="wght" stylevalue="500" /> + </font> + <font weight="600" style="normal">NotoSansEthiopic-VF.ttf + <axis tag="wght" stylevalue="600" /> + </font> + <font weight="700" style="normal">NotoSansEthiopic-VF.ttf + <axis tag="wght" stylevalue="700" /> + </font> + <font weight="400" style="normal" fallbackFor="serif">NotoSerifEthiopic-VF.ttf + <axis tag="wght" stylevalue="400" /> + </font> + <font weight="500" style="normal" fallbackFor="serif">NotoSerifEthiopic-VF.ttf + <axis tag="wght" stylevalue="500" /> + </font> + <font weight="600" style="normal" fallbackFor="serif">NotoSerifEthiopic-VF.ttf + <axis tag="wght" stylevalue="600" /> + </font> + <font weight="700" style="normal" fallbackFor="serif">NotoSerifEthiopic-VF.ttf + <axis tag="wght" stylevalue="700" /> + </font> </family> <family lang="und-Hebr"> <font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font> diff --git a/data/keyboards/Vendor_2378_Product_1008.kl b/data/keyboards/Vendor_2378_Product_1008.kl index 478da03b9a78..7b19469ab6b4 100644 --- a/data/keyboards/Vendor_2378_Product_1008.kl +++ b/data/keyboards/Vendor_2378_Product_1008.kl @@ -14,6 +14,10 @@ # OnLive, Inc. OnLive Wireless Controller, USB adapter +key 164 MEDIA_PLAY_PAUSE +key 167 MEDIA_RECORD +key 168 MEDIA_REWIND +key 208 MEDIA_FAST_FORWARD key 304 BUTTON_A key 305 BUTTON_B key 307 BUTTON_X @@ -22,6 +26,7 @@ key 310 BUTTON_L1 key 311 BUTTON_R1 key 315 BUTTON_START key 314 BUTTON_SELECT +key 316 BUTTON_MODE key 317 BUTTON_THUMBL key 318 BUTTON_THUMBR diff --git a/data/keyboards/Vendor_2378_Product_100a.kl b/data/keyboards/Vendor_2378_Product_100a.kl index d9cd17120464..cb2b73afee3d 100644 --- a/data/keyboards/Vendor_2378_Product_100a.kl +++ b/data/keyboards/Vendor_2378_Product_100a.kl @@ -14,6 +14,10 @@ # OnLive, Inc. OnLive Wireless Controller +key 164 MEDIA_PLAY_PAUSE +key 167 MEDIA_RECORD +key 168 MEDIA_REWIND +key 208 MEDIA_FAST_FORWARD key 304 BUTTON_A key 305 BUTTON_B key 307 BUTTON_X @@ -22,6 +26,7 @@ key 310 BUTTON_L1 key 311 BUTTON_R1 key 315 BUTTON_START key 314 BUTTON_SELECT +key 316 BUTTON_MODE key 317 BUTTON_THUMBL key 318 BUTTON_THUMBR diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java new file mode 100644 index 000000000000..d5243164abdc --- /dev/null +++ b/errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.errorprone.bugpatterns.android; + +import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; +import static com.google.errorprone.matchers.Matchers.allOf; +import static com.google.errorprone.matchers.Matchers.enclosingClass; +import static com.google.errorprone.matchers.Matchers.enclosingMethod; +import static com.google.errorprone.matchers.Matchers.instanceMethod; +import static com.google.errorprone.matchers.Matchers.isSubtypeOf; +import static com.google.errorprone.matchers.Matchers.methodInvocation; +import static com.google.errorprone.matchers.Matchers.methodIsNamed; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.Tree; + +/** + * Parcelable data can be transported in many ways (some of which can be very + * inefficient) so this checker guides developers towards using high-performance + * best-practices. + */ +@AutoService(BugChecker.class) +@BugPattern( + name = "AndroidFrameworkParcelablePerformance", + summary = "Verifies Parcelable performance best-practices", + severity = WARNING) +public final class ParcelablePerformanceChecker extends BugChecker + implements MethodInvocationTreeMatcher { + private static final Matcher<Tree> INSIDE_WRITE_TO_PARCEL = allOf( + enclosingClass(isSubtypeOf("android.os.Parcelable")), + enclosingMethod(methodIsNamed("writeToParcel"))); + + private static final Matcher<ExpressionTree> WRITE_STRING = methodInvocation( + instanceMethod().onExactClass("android.os.Parcel").named("writeString")); + private static final Matcher<ExpressionTree> WRITE_STRING_ARRAY = methodInvocation( + instanceMethod().onExactClass("android.os.Parcel").named("writeStringArray")); + + private static final Matcher<ExpressionTree> WRITE_VALUE = methodInvocation( + instanceMethod().onExactClass("android.os.Parcel").named("writeValue")); + private static final Matcher<ExpressionTree> WRITE_PARCELABLE = methodInvocation( + instanceMethod().onExactClass("android.os.Parcel").named("writeParcelable")); + + private static final Matcher<ExpressionTree> WRITE_LIST = methodInvocation( + instanceMethod().onExactClass("android.os.Parcel").named("writeList")); + private static final Matcher<ExpressionTree> WRITE_PARCELABLE_LIST = methodInvocation( + instanceMethod().onExactClass("android.os.Parcel").named("writeParcelableList")); + private static final Matcher<ExpressionTree> WRITE_PARCELABLE_ARRAY = methodInvocation( + instanceMethod().onExactClass("android.os.Parcel").named("writeParcelableArray")); + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + if (INSIDE_WRITE_TO_PARCEL.matches(tree, state)) { + if (WRITE_STRING.matches(tree, state)) { + return buildDescription(tree) + .setMessage("Recommended to use 'writeString8()' to improve " + + "efficiency; sending as UTF-8 can double throughput") + .build(); + } + if (WRITE_STRING_ARRAY.matches(tree, state)) { + return buildDescription(tree) + .setMessage("Recommended to use 'writeString8Array()' to improve " + + "efficiency; sending as UTF-8 can double throughput") + .build(); + } + + if (WRITE_VALUE.matches(tree, state)) { + return buildDescription(tree) + .setMessage("Recommended to use strongly-typed methods to improve " + + "efficiency; saves 4 bytes for type and overhead of " + + "Parcelable class name") + .build(); + } + if (WRITE_PARCELABLE.matches(tree, state)) { + return buildDescription(tree) + .setMessage("Recommended to use 'item.writeToParcel()' to improve " + + "efficiency; saves overhead of Parcelable class name") + .build(); + } + + if (WRITE_LIST.matches(tree, state)) { + return buildDescription(tree) + .setMessage("Recommended to use 'writeTypedList()' to improve " + + "efficiency; saves overhead of repeated Parcelable class name") + .build(); + } + if (WRITE_PARCELABLE_LIST.matches(tree, state)) { + return buildDescription(tree) + .setMessage("Recommended to use 'writeTypedList()' to improve " + + "efficiency; saves overhead of repeated Parcelable class name") + .build(); + } + if (WRITE_PARCELABLE_ARRAY.matches(tree, state)) { + return buildDescription(tree) + .setMessage("Recommended to use 'writeTypedArray()' to improve " + + "efficiency; saves overhead of repeated Parcelable class name") + .build(); + } + } + return Description.NO_MATCH; + } +} diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.java new file mode 100644 index 000000000000..75c76e32f00e --- /dev/null +++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.java @@ -0,0 +1,105 @@ +/* + * 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.google.errorprone.bugpatterns.android; + +import com.google.errorprone.CompilationTestHelper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ParcelablePerformanceCheckerTest { + private CompilationTestHelper compilationHelper; + + @Before + public void setUp() { + compilationHelper = CompilationTestHelper.newInstance( + ParcelablePerformanceChecker.class, getClass()); + } + + @Test + public void testString() { + compilationHelper + .addSourceFile("/android/os/Parcel.java") + .addSourceFile("/android/os/Parcelable.java") + .addSourceLines("FooInfo.java", + "import android.os.Parcel;", + "import android.os.Parcelable;", + "public class FooInfo implements Parcelable {", + " public void writeToParcel(Parcel dest, int flags) {", + " // BUG: Diagnostic contains:", + " dest.writeString(toString());", + " dest.writeString8(toString());", + " // BUG: Diagnostic contains:", + " dest.writeStringArray(new String[0]);", + " dest.writeString8Array(new String[0]);", + " }", + "}") + .doTest(); + } + + @Test + public void testSingle() { + compilationHelper + .addSourceFile("/android/os/Parcel.java") + .addSourceFile("/android/os/Parcelable.java") + .addSourceLines("FooInfo.java", + "import android.os.Parcel;", + "import android.os.Parcelable;", + "public class FooInfo implements Parcelable {", + " public void writeToParcel(Parcel dest, int flags) {", + " // BUG: Diagnostic contains:", + " dest.writeValue(this);", + " this.writeToParcel(dest, flags);", + " // BUG: Diagnostic contains:", + " dest.writeParcelable(this, flags);", + " this.writeToParcel(dest, flags);", + " }", + "}") + .doTest(); + } + + @Test + public void testList() { + compilationHelper + .addSourceFile("/android/os/Parcel.java") + .addSourceFile("/android/os/Parcelable.java") + .addSourceLines("FooInfo.java", + "import android.os.Parcel;", + "import android.os.Parcelable;", + "import java.util.List;", + "import java.util.ArrayList;", + "public class FooInfo implements Parcelable {", + " public void writeToParcel(Parcel dest, int flags) {", + " List<Parcelable> list = new ArrayList<Parcelable>();", + " Parcelable[] array = new Parcelable[0];", + " // BUG: Diagnostic contains:", + " dest.writeList(list);", + " dest.writeTypedList(list, flags);", + " // BUG: Diagnostic contains:", + " dest.writeParcelableList(list, flags);", + " dest.writeTypedList(list, flags);", + " // BUG: Diagnostic contains:", + " dest.writeParcelableArray(array, flags);", + " dest.writeTypedArray(array, flags);", + " }", + "}") + .doTest(); + } +} diff --git a/errorprone/tests/res/android/os/Parcel.java b/errorprone/tests/res/android/os/Parcel.java new file mode 100644 index 000000000000..bafa23626fb2 --- /dev/null +++ b/errorprone/tests/res/android/os/Parcel.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import java.util.List; + +public class Parcel { + public void writeString(String val) { + throw new UnsupportedOperationException(); + } + public void writeString8(String val) { + throw new UnsupportedOperationException(); + } + public final void writeStringArray(String[] val) { + throw new UnsupportedOperationException(); + } + public final void writeString8Array(String[] val) { + throw new UnsupportedOperationException(); + } + + public final void writeValue(Object v) { + throw new UnsupportedOperationException(); + } + public final void writeParcelable(Parcelable p, int flags) { + throw new UnsupportedOperationException(); + } + + public final void writeList(List val) { + throw new UnsupportedOperationException(); + } + public final <T extends Parcelable> void writeParcelableList(List<T> val, int flags) { + throw new UnsupportedOperationException(); + } + public <T extends Parcelable> void writeTypedList(List<T> val, int flags) { + throw new UnsupportedOperationException(); + } + public final <T extends Parcelable> void writeParcelableArray(T[] value, int flags) { + throw new UnsupportedOperationException(); + } + public final <T extends Parcelable> void writeTypedArray(T[] val, int flags) { + throw new UnsupportedOperationException(); + } +} diff --git a/errorprone/tests/res/android/os/Parcelable.java b/errorprone/tests/res/android/os/Parcelable.java new file mode 100644 index 000000000000..217690d69fd6 --- /dev/null +++ b/errorprone/tests/res/android/os/Parcelable.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +public interface Parcelable { + public void writeToParcel(Parcel dest, int flags); +} diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 4cd55e8ea210..4c0f890eee40 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -26,17 +26,17 @@ public final class BLASTBufferQueue { // Note: This field is accessed by native code. public long mNativeObject; // BLASTBufferQueue* - private static native long nativeCreate(long surfaceControl, long width, long height, - boolean tripleBufferingEnabled); + private static native long nativeCreate(String name, long surfaceControl, long width, + long height, boolean tripleBufferingEnabled); private static native void nativeDestroy(long ptr); private static native Surface nativeGetSurface(long ptr); private static native void nativeSetNextTransaction(long ptr, long transactionPtr); private static native void nativeUpdate(long ptr, long surfaceControl, long width, long height); /** Create a new connection with the surface flinger. */ - public BLASTBufferQueue(SurfaceControl sc, int width, int height, + public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height, boolean tripleBufferingEnabled) { - mNativeObject = nativeCreate(sc.mNativeObject, width, height, tripleBufferingEnabled); + mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, tripleBufferingEnabled); } public void destroy() { diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 964640b106b9..28d7911c771f 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -3111,7 +3111,7 @@ public class Paint { @CriticalNative private static native boolean nGetFillPath(long paintPtr, long src, long dst); @CriticalNative - private static native void nSetShader(long paintPtr, long shader); + private static native long nSetShader(long paintPtr, long shader); @CriticalNative private static native long nSetColorFilter(long paintPtr, long filter); @CriticalNative diff --git a/graphics/java/android/graphics/RenderEffect.java b/graphics/java/android/graphics/RenderEffect.java new file mode 100644 index 000000000000..9fc0c8eb9d90 --- /dev/null +++ b/graphics/java/android/graphics/RenderEffect.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 android.graphics; + +import android.annotation.NonNull; +import android.graphics.Shader.TileMode; + +import libcore.util.NativeAllocationRegistry; + +/** + * Intermediate rendering step used to render drawing commands with a corresponding + * visual effect + * + * @hide + */ +public final class RenderEffect { + + private static class RenderEffectHolder { + public static final NativeAllocationRegistry RENDER_EFFECT_REGISTRY = + NativeAllocationRegistry.createMalloced( + RenderEffect.class.getClassLoader(), nativeGetFinalizer()); + } + + /** + * Create a {@link RenderEffect} instance that will offset the drawing content + * by the provided x and y offset. + * @param offsetX offset along the x axis in pixels + * @param offsetY offset along the y axis in pixels + */ + @NonNull + public static RenderEffect createOffsetEffect(float offsetX, float offsetY) { + return new RenderEffect(nativeCreateOffsetEffect(offsetX, offsetY, 0)); + } + + /** + * Create a {@link RenderEffect} instance with the provided x and y offset + * @param offsetX offset along the x axis in pixels + * @param offsetY offset along the y axis in pixels + * @param input target RenderEffect used to render in the offset coordinates. + */ + @NonNull + public static RenderEffect createOffsetEffect( + float offsetX, + float offsetY, + @NonNull RenderEffect input + ) { + return new RenderEffect(nativeCreateOffsetEffect( + offsetX, + offsetY, + input.getNativeInstance() + ) + ); + } + + /** + * Create a {@link RenderEffect} that blurs the contents of the optional input RenderEffect + * with the specified radius along the x and y axis. If no input RenderEffect is provided + * then all drawing commands issued with a {@link android.graphics.RenderNode} that this + * RenderEffect is installed in will be blurred + * @param radiusX Radius of blur along the X axis + * @param radiusY Radius of blur along the Y axis + * @param inputEffect Input RenderEffect that provides the content to be blurred, can be null + * to indicate that the drawing commands on the RenderNode are to be + * blurred instead of the input RenderEffect + * @param edgeTreatment Policy for how to blur content near edges of the blur kernel + */ + @NonNull + public static RenderEffect createBlurEffect( + float radiusX, + float radiusY, + @NonNull RenderEffect inputEffect, + @NonNull TileMode edgeTreatment + ) { + long nativeInputEffect = inputEffect != null ? inputEffect.mNativeRenderEffect : 0; + return new RenderEffect( + nativeCreateBlurEffect( + radiusX, + radiusY, + nativeInputEffect, + edgeTreatment.nativeInt + ) + ); + } + + /** + * Create a {@link RenderEffect} that blurs the contents of the + * {@link android.graphics.RenderNode} that this RenderEffect is installed on with the + * specified radius along hte x and y axis. + * @param radiusX Radius of blur along the X axis + * @param radiusY Radius of blur along the Y axis + * @param edgeTreatment Policy for how to blur content near edges of the blur kernel + */ + @NonNull + public static RenderEffect createBlurEffect( + float radiusX, + float radiusY, + @NonNull TileMode edgeTreatment + ) { + return new RenderEffect( + nativeCreateBlurEffect( + radiusX, + radiusY, + 0, + edgeTreatment.nativeInt + ) + ); + } + + private final long mNativeRenderEffect; + + /* only constructed from static factory methods */ + private RenderEffect(long nativeRenderEffect) { + mNativeRenderEffect = nativeRenderEffect; + RenderEffectHolder.RENDER_EFFECT_REGISTRY.registerNativeAllocation( + this, mNativeRenderEffect); + } + + /** + * Obtain the pointer to the underlying RenderEffect to be configured + * on a RenderNode object via {@link RenderNode#setRenderEffect(RenderEffect)} + */ + /* package */ long getNativeInstance() { + return mNativeRenderEffect; + } + + private static native long nativeCreateOffsetEffect( + float offsetX, float offsetY, long nativeInput); + private static native long nativeCreateBlurEffect( + float radiusX, float radiusY, long nativeInput, int edgeTreatment); + private static native long nativeGetFinalizer(); +} diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index 8aacbc7bc109..d812c1a5595d 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -850,6 +850,23 @@ public final class RenderNode { } /** + * Configure the {@link android.graphics.RenderEffect} to apply to this RenderNode. This + * will apply a visual effect to the end result of the contents of this RenderNode before + * it is drawn into the destination. For example if + * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, Shader.TileMode)} + * is provided, the contents will be drawn in a separate layer, then this layer will + * be blurred when this RenderNode is drawn into the destination. + * @param renderEffect to be applied to the RenderNode. Passing null clears all previously + * configured RenderEffects + * + * @hide + */ + public void setRenderEffect(@Nullable RenderEffect renderEffect) { + nSetRenderEffect(mNativeRenderNode, + renderEffect != null ? renderEffect.getNativeInstance() : 0); + } + + /** * Returns the translucency level of this display list. * * @return A value between 0.0f and 1.0f @@ -1655,6 +1672,9 @@ public final class RenderNode { private static native boolean nSetAlpha(long renderNode, float alpha); @CriticalNative + private static native void nSetRenderEffect(long renderNode, long renderEffect); + + @CriticalNative private static native boolean nSetHasOverlappingRendering(long renderNode, boolean hasOverlappingRendering); diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java index cbae67507a64..e1a17957ebd7 100644 --- a/graphics/java/android/graphics/fonts/Font.java +++ b/graphics/java/android/graphics/fonts/Font.java @@ -26,8 +26,11 @@ import android.graphics.Paint; import android.graphics.RectF; import android.os.LocaleList; import android.os.ParcelFileDescriptor; +import android.util.Log; +import android.util.LongSparseArray; import android.util.TypedValue; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.CriticalNative; @@ -40,6 +43,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; @@ -56,6 +60,15 @@ public final class Font { private static final int STYLE_ITALIC = 1; private static final int STYLE_NORMAL = 0; + private static final Object MAP_LOCK = new Object(); + // We need to have mapping from native ptr to Font object for later accessing from TextShape + // result since Typeface doesn't have reference to Font object and it is not always created from + // Font object. Sometimes Typeface is created in native layer only and there might not be Font + // object in Java layer. So, if not found in this cache, create new Font object for API user. + @GuardedBy("MAP_LOCK") + private static final LongSparseArray<WeakReference<Font>> FONT_PTR_MAP = + new LongSparseArray<>(); + /** * A builder class for creating new Font. */ @@ -501,6 +514,10 @@ public final class Font { mTtcIndex = ttcIndex; mAxes = axes; mLocaleList = localeList; + + synchronized (MAP_LOCK) { + FONT_PTR_MAP.append(mNativePtr, new WeakReference<>(this)); + } } /** @@ -574,14 +591,14 @@ public final class Font { * * @param glyphId a glyph ID * @param paint a paint object used for resolving glyph style - * @param rect a nullable destination object. If null is passed, this function just return the - * horizontal advance. If non-null is passed, this function fills bounding box - * information to this object. + * @param outBoundingBox a nullable destination object. If null is passed, this function just + * return the horizontal advance. If non-null is passed, this function + * fills bounding box information to this object. * @return the amount of horizontal advance in pixels */ public float getGlyphBounds(@IntRange(from = 0) int glyphId, @NonNull Paint paint, - @Nullable RectF rect) { - return nGetGlyphBounds(mNativePtr, glyphId, paint.getNativeInstance(), rect); + @Nullable RectF outBoundingBox) { + return nGetGlyphBounds(mNativePtr, glyphId, paint.getNativeInstance(), outBoundingBox); } /** @@ -590,15 +607,15 @@ public final class Font { * Note that {@link android.graphics.Typeface} in {@link android.graphics.Paint} is ignored. * * @param paint a paint object used for retrieving font metrics. - * @param metrics a nullable destination object. If null is passed, this function only retrieve - * recommended interline spacing. If non-null is passed, this function fills to - * font metrics to it. + * @param outMetrics a nullable destination object. If null is passed, this function only + * retrieve recommended interline spacing. If non-null is passed, this function + * fills to font metrics to it. * * @see Paint#getFontMetrics() * @see Paint#getFontMetricsInt() */ - public void getMetrics(@NonNull Paint paint, @Nullable Paint.FontMetrics metrics) { - nGetFontMetrics(mNativePtr, paint.getNativeInstance(), metrics); + public void getMetrics(@NonNull Paint paint, @Nullable Paint.FontMetrics outMetrics) { + nGetFontMetrics(mNativePtr, paint.getNativeInstance(), outMetrics); } /** @hide */ @@ -637,6 +654,63 @@ public final class Font { + "}"; } + /** + * Lookup Font object from native pointer or create new one if not found. + * @hide + */ + public static Font findOrCreateFontFromNativePtr(long ptr) { + // First, lookup from known mapps. + synchronized (MAP_LOCK) { + WeakReference<Font> fontRef = FONT_PTR_MAP.get(ptr); + if (fontRef != null) { + Font font = fontRef.get(); + if (font != null) { + return font; + } + } + + // If not found, create Font object from native object for Java API users. + ByteBuffer buffer = NativeFontBufferHelper.refByteBuffer(ptr); + long packed = nGetFontInfo(ptr); + int weight = (int) (packed & 0x0000_0000_0000_FFFFL); + boolean italic = (packed & 0x0000_0000_0001_0000L) != 0; + int ttcIndex = (int) ((packed & 0x0000_FFFF_0000_0000L) >> 32); + int axisCount = (int) ((packed & 0xFFFF_0000_0000_0000L) >> 48); + FontVariationAxis[] axes = new FontVariationAxis[axisCount]; + char[] charBuffer = new char[4]; + for (int i = 0; i < axisCount; ++i) { + long packedAxis = nGetAxisInfo(ptr, i); + float value = Float.intBitsToFloat((int) (packedAxis & 0x0000_0000_FFFF_FFFFL)); + charBuffer[0] = (char) ((packedAxis & 0xFF00_0000_0000_0000L) >> 56); + charBuffer[1] = (char) ((packedAxis & 0x00FF_0000_0000_0000L) >> 48); + charBuffer[2] = (char) ((packedAxis & 0x0000_FF00_0000_0000L) >> 40); + charBuffer[3] = (char) ((packedAxis & 0x0000_00FF_0000_0000L) >> 32); + axes[i] = new FontVariationAxis(new String(charBuffer), value); + } + Font.Builder builder = new Font.Builder(buffer) + .setWeight(weight) + .setSlant(italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT) + .setTtcIndex(ttcIndex) + .setFontVariationSettings(axes); + + Font newFont = null; + try { + newFont = builder.build(); + FONT_PTR_MAP.append(ptr, new WeakReference<>(newFont)); + } catch (IOException e) { + // This must not happen since the buffer was already created once. + Log.e("Font", "Failed to create font object from existing buffer.", e); + } + return newFont; + } + } + + @CriticalNative + private static native long nGetFontInfo(long ptr); + + @CriticalNative + private static native long nGetAxisInfo(long ptr, int i); + @FastNative private static native float nGetGlyphBounds(long font, int glyphId, long paint, RectF rect); diff --git a/graphics/java/android/graphics/fonts/NativeFontBufferHelper.java b/graphics/java/android/graphics/fonts/NativeFontBufferHelper.java new file mode 100644 index 000000000000..5655e7fafc1b --- /dev/null +++ b/graphics/java/android/graphics/fonts/NativeFontBufferHelper.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 android.graphics.fonts; + +import android.annotation.NonNull; + +import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.FastNative; + +import libcore.util.NativeAllocationRegistry; + +import java.nio.ByteBuffer; + +/** + * This is a helper class for showing native allocated buffer in Java API. + * + * @hide + */ +public class NativeFontBufferHelper { + private NativeFontBufferHelper() {} + + private static final NativeAllocationRegistry REGISTRY = + NativeAllocationRegistry.createMalloced( + ByteBuffer.class.getClassLoader(), nGetReleaseFunc()); + + /** + * Wrap native buffer with ByteBuffer with adding reference to it. + */ + public static @NonNull ByteBuffer refByteBuffer(long fontPtr) { + long refPtr = nRefFontBuffer(fontPtr); + ByteBuffer buffer = nWrapByteBuffer(refPtr); + + // Releasing native object so that decreasing shared pointer ref count when the byte buffer + // is GCed. + REGISTRY.registerNativeAllocation(buffer, refPtr); + + return buffer; + } + + @CriticalNative + private static native long nRefFontBuffer(long fontPtr); + + @FastNative + private static native ByteBuffer nWrapByteBuffer(long refPtr); + + @CriticalNative + private static native long nGetReleaseFunc(); +} diff --git a/graphics/java/android/graphics/text/GlyphStyle.java b/graphics/java/android/graphics/text/GlyphStyle.java new file mode 100644 index 000000000000..cc8c4d26fb5e --- /dev/null +++ b/graphics/java/android/graphics/text/GlyphStyle.java @@ -0,0 +1,234 @@ +/* + * 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.graphics.text; + +import android.annotation.ColorInt; +import android.annotation.FloatRange; +import android.annotation.NonNull; +import android.graphics.Paint; + +import java.util.Objects; + +/** + * Represents subset of Paint parameters such as font size, scaleX that is used to draw a glyph. + * + * Glyph is a most primitive unit of text drawing. + * + */ +public class GlyphStyle { + private @ColorInt int mColor; + private float mFontSize; + private float mScaleX; + private float mSkewX; + private int mFlags; + + /** + * @param color a color. + * @param fontSize a font size in pixels. + * @param scaleX a horizontal scale factor. + * @param skewX a horizontal skew factor + * @param flags paint flags + * + * @see Paint#getFlags() + * @see Paint#setFlags(int) + */ + public GlyphStyle( + @ColorInt int color, + @FloatRange(from = 0) float fontSize, + @FloatRange(from = 0) float scaleX, + @FloatRange(from = 0) float skewX, + int flags) { + mColor = color; + mFontSize = fontSize; + mScaleX = scaleX; + mSkewX = skewX; + mFlags = flags; + } + + /** + * Create glyph style from Paint + * + * @param paint a paint + */ + public GlyphStyle(@NonNull Paint paint) { + setFromPaint(paint); + } + + /** + * Gets the color. + * + * @return a color + * @see Paint#getColor() + * @see Paint#setColor(int) + */ + public @ColorInt int getColor() { + return mColor; + } + + /** + * Sets the color. + * + * @param color a color + * @see Paint#getColor() + * @see Paint#setColor(int) + */ + public void setColor(@ColorInt int color) { + mColor = color; + } + + /** + * Gets the font size in pixels. + * + * @return font size + * @see Paint#getTextSize() + * @see Paint#setTextSize(float) + */ + public @FloatRange(from = 0) float getFontSize() { + return mFontSize; + } + + /** + * Sets the font size in pixels. + * + * @param fontSize font size in pixel + * @see Paint#getTextSize() + * @see Paint#setTextSize(float) + */ + public void setFontSize(@FloatRange(from = 0) float fontSize) { + mFontSize = fontSize; + } + + /** + * Return the horizontal scale factor + * + * @return a horizontal scale factor + * @see Paint#getTextScaleX() + * @see Paint#setTextScaleX(float) + */ + public @FloatRange(from = 0) float getScaleX() { + return mScaleX; + } + + /** + * Set the horizontal scale factor + * + * @param scaleX a horizontal scale factor + * @see Paint#getTextScaleX() + * @see Paint#setTextScaleX(float) + */ + public void setScaleX(@FloatRange(from = 0) float scaleX) { + mScaleX = scaleX; + } + + /** + * Return the horizontal skew factor + * + * @return a horizontal skew factor + * @see Paint#getTextSkewX() + * @see Paint#setTextSkewX(float) + */ + public @FloatRange(from = 0) float getSkewX() { + return mSkewX; + } + + /** + * Set the horizontal skew factor + * + * @param skewX a horizontal skew factor + * @see Paint#getTextSkewX() + * @see Paint#setTextSkewX(float) + */ + public void setSkewX(@FloatRange(from = 0) float skewX) { + mSkewX = skewX; + } + + /** + * Returns the Paint flags. + * + * @return a paint flags + * @see Paint#getFlags() + * @see Paint#setFlags(int) + */ + public int getFlags() { + return mFlags; + } + + /** + * Set the Paint flags. + * + * @param flags a paint flags + * @see Paint#getFlags() + * @see Paint#setFlags(int) + */ + public void setFlags(int flags) { + mFlags = flags; + } + + /** + * Applies glyph style to the paint object. + * + * @param paint a paint object + */ + public void applyToPaint(@NonNull Paint paint) { + paint.setColor(mColor); + paint.setTextSize(mFontSize); + paint.setTextScaleX(mScaleX); + paint.setTextSkewX(mSkewX); + paint.setFlags(mFlags); + } + + /** + * Copy parameters from a Paint object. + * + * @param paint a paint object + */ + public void setFromPaint(@NonNull Paint paint) { + mColor = paint.getColor(); + mFontSize = paint.getTextSize(); + mScaleX = paint.getTextScaleX(); + mSkewX = paint.getTextSkewX(); + mFlags = paint.getFlags(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GlyphStyle)) return false; + GlyphStyle that = (GlyphStyle) o; + return that.mColor == mColor + && Float.compare(that.mFontSize, mFontSize) == 0 + && Float.compare(that.mScaleX, mScaleX) == 0 + && Float.compare(that.mSkewX, mSkewX) == 0 + && mFlags == that.mFlags; + } + + @Override + public int hashCode() { + return Objects.hash(mColor, mFontSize, mScaleX, mSkewX, mFlags); + } + + @Override + public String toString() { + return "GlyphStyle{" + + "mColor=" + mColor + + ", mFontSize=" + mFontSize + + ", mScaleX=" + mScaleX + + ", mSkewX=" + mSkewX + + ", mFlags=" + mFlags + + '}'; + } +} diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java new file mode 100644 index 000000000000..7364d545a452 --- /dev/null +++ b/graphics/java/android/graphics/text/PositionedGlyphs.java @@ -0,0 +1,277 @@ +/* + * 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.graphics.text; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.graphics.fonts.Font; + +import com.android.internal.util.Preconditions; + +import dalvik.annotation.optimization.CriticalNative; + +import libcore.util.NativeAllocationRegistry; + +import java.util.ArrayList; +import java.util.Objects; + +/** + * Text shaping result object for single style text. + * + * You can get text shaping result by + * {@link TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)} and + * {@link TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}. + * + * @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint) + * @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint) + */ +public final class PositionedGlyphs { + private static final NativeAllocationRegistry REGISTRY = + NativeAllocationRegistry.createMalloced( + Typeface.class.getClassLoader(), nReleaseFunc()); + + private final long mLayoutPtr; + private final float mXOffset; + private final float mYOffset; + private final GlyphStyle mGlyphStyle; + private final ArrayList<Font> mFonts; + + /** + * Returns the total amount of advance consumed by this positioned glyphs. + * + * The advance is an amount of width consumed by the glyph. The total amount of advance is + * a total amount of advance consumed by this series of glyphs. In other words, if another + * glyph is placed next to this series of glyphs, it's X offset should be shifted this amount + * of width. + * + * @return total amount of advance + */ + public float getTotalAdvance() { + return nGetTotalAdvance(mLayoutPtr); + } + + /** + * Effective ascent value of this positioned glyphs. + * + * If two or more font files are used in this series of glyphs, the effective ascent will be + * the minimum ascent value across the all font files. + * + * @return effective ascent value + */ + public float getAscent() { + return nGetAscent(mLayoutPtr); + } + + /** + * Effective descent value of this positioned glyphs. + * + * If two or more font files are used in this series of glyphs, the effective descent will be + * the maximum descent value across the all font files. + * + * @return effective descent value + */ + public float getDescent() { + return nGetDescent(mLayoutPtr); + } + + /** + * Returns the glyph style used for drawing the glyph at the given index. + * + * @return A glyph style + */ + @NonNull + public GlyphStyle getStyle() { + return mGlyphStyle; + } + + /** + * Returns the amount of X offset added to glyph position. + * + * @return The X offset added to glyph position. + */ + public float getOriginX() { + return mXOffset; + } + + /** + * Returns the amount of Y offset added to glyph position. + * + * @return The Y offset added to glyph position. + */ + public float getOriginY() { + return mYOffset; + } + + /** + * Returns the number of glyphs stored. + * + * @return the number of glyphs + */ + @IntRange(from = 0) + public int glyphCount() { + return nGetGlyphCount(mLayoutPtr); + } + + /** + * Returns the font object used for drawing the glyph at the given index. + * + * @param index the glyph index + * @return the font object used for drawing the glyph at the given index + */ + @NonNull + public Font getFont(@IntRange(from = 0) int index) { + Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + return mFonts.get(index); + } + + /** + * Returns the glyph ID used for drawing the glyph at the given index. + * + * @param index the glyph index + * @return A font object + */ + @IntRange(from = 0) + public int getGlyphId(@IntRange(from = 0) int index) { + Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + return nGetGlyphId(mLayoutPtr, index); + } + + /** + * Returns the x coordinate of the glyph position at the given index. + * + * @param index the glyph index + * @return A X offset in pixels + */ + public float getPositionX(@IntRange(from = 0) int index) { + Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + return nGetX(mLayoutPtr, index) + mXOffset; + } + + /** + * Returns the y coordinate of the glyph position at the given index. + * + * @param index the glyph index + * @return A Y offset in pixels. + */ + public float getPositionY(@IntRange(from = 0) int index) { + Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + return nGetY(mLayoutPtr, index) + mYOffset; + } + + /** + * Create single style layout from native result. + * + * @hide + * + * @param layoutPtr the address of native layout object. + * @param paint a paint object + */ + public PositionedGlyphs(long layoutPtr, @NonNull Paint paint, float xOffset, float yOffset) { + mLayoutPtr = layoutPtr; + mGlyphStyle = new GlyphStyle(paint); + int glyphCount = nGetGlyphCount(layoutPtr); + mFonts = new ArrayList<>(glyphCount); + mXOffset = xOffset; + mYOffset = yOffset; + + long prevPtr = 0; + Font prevFont = null; + for (int i = 0; i < glyphCount; ++i) { + long ptr = nGetFont(layoutPtr, i); + if (prevPtr != ptr) { + prevPtr = ptr; + prevFont = Font.findOrCreateFontFromNativePtr(ptr); + } + mFonts.add(prevFont); + } + + REGISTRY.registerNativeAllocation(this, layoutPtr); + } + + @CriticalNative + private static native int nGetGlyphCount(long minikinLayout); + @CriticalNative + private static native float nGetTotalAdvance(long minikinLayout); + @CriticalNative + private static native float nGetAscent(long minikinLayout); + @CriticalNative + private static native float nGetDescent(long minikinLayout); + @CriticalNative + private static native int nGetGlyphId(long minikinLayout, int i); + @CriticalNative + private static native float nGetX(long minikinLayout, int i); + @CriticalNative + private static native float nGetY(long minikinLayout, int i); + @CriticalNative + private static native long nGetFont(long minikinLayout, int i); + @CriticalNative + private static native long nReleaseFunc(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PositionedGlyphs)) return false; + PositionedGlyphs that = (PositionedGlyphs) o; + + if (!mGlyphStyle.equals(that.mGlyphStyle)) return false; + if (mXOffset != that.mXOffset || mYOffset != that.mYOffset) return false; + if (glyphCount() != that.glyphCount()) return false; + + for (int i = 0; i < glyphCount(); ++i) { + if (getGlyphId(i) != that.getGlyphId(i)) return false; + if (getPositionX(i) != that.getPositionX(i)) return false; + if (getPositionY(i) != that.getPositionY(i)) return false; + // Intentionally using reference equality since font equality is heavy due to buffer + // compare. + if (getFont(i) != that.getFont(i)) return false; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = Objects.hash(mXOffset, mYOffset, mGlyphStyle); + for (int i = 0; i < glyphCount(); ++i) { + hashCode = Objects.hash(hashCode, + getGlyphId(i), getPositionX(i), getPositionY(i), getFont(i)); + } + return hashCode; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < glyphCount(); ++i) { + if (i != 0) { + sb.append(", "); + } + sb.append("[ ID = " + getGlyphId(i) + "," + + " pos = (" + getPositionX(i) + "," + getPositionY(i) + ")" + + " font = " + getFont(i) + " ]"); + } + sb.append("]"); + return "PositionedGlyphs{" + + "glyphs = " + sb.toString() + + ", mXOffset=" + mXOffset + + ", mYOffset=" + mYOffset + + ", mGlyphStyle=" + mGlyphStyle + + '}'; + } +} diff --git a/graphics/java/android/graphics/text/TextShaper.java b/graphics/java/android/graphics/text/TextShaper.java new file mode 100644 index 000000000000..f40ed8f8f653 --- /dev/null +++ b/graphics/java/android/graphics/text/TextShaper.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 android.graphics.text; + +import android.annotation.NonNull; +import android.graphics.Paint; +import android.text.TextDirectionHeuristic; +import android.text.TextUtils; + +import com.android.internal.util.Preconditions; + +import dalvik.annotation.optimization.FastNative; + +/** + * Provides conversion from a text into glyph array. + * + * Text shaping is a preprocess for drawing text into canvas with glyphs. The glyph is a most + * primitive unit of the text drawing, consist of glyph identifier in the font file and its position + * and style. You can draw the shape result to Canvas by calling Canvas#drawGlyphs. + + * + * @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint) + * @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint) + * @see android.text.StyledTextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, + * TextPaint) + */ +public class TextShaper { + private TextShaper() {} // Do not instantiate + + /** + * Shape non-styled text. + * + * This function shapes the text of the given range under the context of given context range. + * Some script, e.g. Arabic or Devanagari, changes letter shape based on its location or + * surrounding characters. + * + * @param text a text buffer to be shaped + * @param start a start index of shaping target in the buffer. + * @param count a length of shaping target in the buffer. + * @param contextStart a start index of context used for shaping in the buffer. + * @param contextCount a length of context used for shaping in the buffer. + * @param xOffset an additional amount of x offset of the result glyphs. + * @param yOffset an additional amount of y offset of the result glyphs. + * @param isRtl true if this text is shaped for RTL direction, false otherwise. + * @param paint a paint used for shaping text. + * @return a shape result. + */ + @NonNull + public static PositionedGlyphs shapeTextRun( + @NonNull char[] text, int start, int count, int contextStart, int contextCount, + float xOffset, float yOffset, boolean isRtl, @NonNull Paint paint) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(paint); + return new PositionedGlyphs( + nativeShapeTextRun(text, start, count, contextStart, contextCount, isRtl, + paint.getNativeInstance()), + paint, xOffset, yOffset); + } + + /** + * Shape non-styled text. + * + * This function shapes the text of the given range under the context of given context range. + * Some script, e.g. Arabic or Devanagari, changes letter shape based on its location or + * surrounding characters. + * + * @param text a text buffer to be shaped. Any styled spans stored in this text are ignored. + * @param start a start index of shaping target in the buffer. + * @param count a length of shaping target in the buffer. + * @param contextStart a start index of context used for shaping in the buffer. + * @param contextCount a length of context used for shaping in the buffer. + * @param xOffset an additional amount of x offset of the result glyphs. + * @param yOffset an additional amount of y offset of the result glyphs. + * @param isRtl true if this text is shaped for RTL direction, false otherwise. + * @param paint a paint used for shaping text. + * @return a shape result + */ + @NonNull + public static PositionedGlyphs shapeTextRun( + @NonNull CharSequence text, int start, int count, int contextStart, int contextCount, + float xOffset, float yOffset, boolean isRtl, @NonNull Paint paint) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(paint); + if (text instanceof String) { + return new PositionedGlyphs( + nativeShapeTextRun( + (String) text, start, count, contextStart, contextCount, isRtl, + paint.getNativeInstance()), + paint, xOffset, yOffset); + } else { + char[] buf = new char[contextCount]; + TextUtils.getChars(text, contextStart, contextStart + contextCount, buf, 0); + return new PositionedGlyphs( + nativeShapeTextRun( + buf, start - contextStart, count, + 0, contextCount, isRtl, paint.getNativeInstance()), + paint, xOffset, yOffset); + } + } + + @FastNative + private static native long nativeShapeTextRun( + char[] text, int start, int count, int contextStart, int contextCount, + boolean isRtl, long nativePaint); + + @FastNative + private static native long nativeShapeTextRun( + String text, int start, int count, int contextStart, int contextCount, + boolean isRtl, long nativePaint); + +} diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 1591b0616262..0defbd6451fe 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -23,7 +23,18 @@ java_library { filegroup { name: "wm_shell-sources", - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + ], + path: "src", +} + +// TODO(b/168581922) protologtool do not support kotlin(*.kt) +filegroup { + name: "wm_shell-sources-kt", + srcs: [ + "src/**/*.kt", + ], path: "src", } @@ -97,15 +108,23 @@ android_library { name: "WindowManager-Shell", srcs: [ ":wm_shell_protolog_src", + // TODO(b/168581922) protologtool do not support kotlin(*.kt) + ":wm_shell-sources-kt", "src/**/I*.aidl", ], resource_dirs: [ "res", ], static_libs: [ + "androidx.dynamicanimation_dynamicanimation", + "kotlinx-coroutines-android", + "kotlinx-coroutines-core", "protolog-lib", "WindowManager-Shell-proto", "androidx.appcompat_appcompat", ], + kotlincflags: ["-Xjvm-default=enable"], manifest: "AndroidManifest.xml", + + min_sdk_version: "26", }
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/dismiss_circle_background.xml b/libs/WindowManager/Shell/res/drawable/dismiss_circle_background.xml index 7809c8398c2d..7809c8398c2d 100644 --- a/packages/SystemUI/res/drawable/dismiss_circle_background.xml +++ b/libs/WindowManager/Shell/res/drawable/dismiss_circle_background.xml diff --git a/packages/SystemUI/res/drawable/ic_skip_next_white.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_skip_next_white.xml index 040c7e642241..040c7e642241 100644 --- a/packages/SystemUI/res/drawable/ic_skip_next_white.xml +++ b/libs/WindowManager/Shell/res/drawable/pip_ic_skip_next_white.xml diff --git a/packages/SystemUI/res/drawable/ic_skip_previous_white.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_skip_previous_white.xml index b9b94b73a00f..b9b94b73a00f 100644 --- a/packages/SystemUI/res/drawable/ic_skip_previous_white.xml +++ b/libs/WindowManager/Shell/res/drawable/pip_ic_skip_previous_white.xml diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_control_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_control_button.xml index 72287c144bed..727ac3412a25 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_control_button.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_control_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.systemui.pip.tv.PipControlButtonView}. --> +<!-- Layout for {@link com.android.wm.shell.pip.tv.PipControlButtonView}. --> <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_controls.xml b/libs/WindowManager/Shell/res/layout/tv_pip_controls.xml index 22e0452d620d..d2f235e273d5 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_controls.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_controls.xml @@ -14,17 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. --> -<!-- Layout for {@link com.android.systemui.pip.tv.PipControlsView}. --> +<!-- Layout for {@link com.android.wm.shell.pip.tv.PipControlsView}. --> <merge xmlns:android="http://schemas.android.com/apk/res/android"> - <com.android.systemui.pip.tv.PipControlButtonView + <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.systemui.pip.tv.PipControlButtonView + <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" @@ -32,7 +32,7 @@ android:src="@drawable/pip_ic_close_white" android:text="@string/pip_close" /> - <com.android.systemui.pip.tv.PipControlButtonView + <com.android.wm.shell.pip.tv.PipControlButtonView android:id="@+id/play_pause_button" android:layout_width="@dimen/picture_in_picture_button_width" android:layout_height="wrap_content" diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_custom_control.xml b/libs/WindowManager/Shell/res/layout/tv_pip_custom_control.xml index e6cd1122ca77..452f2cd5ccb6 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_custom_control.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_custom_control.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -<com.android.systemui.pip.tv.PipControlButtonView +<com.android.wm.shell.pip.tv.PipControlButtonView 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/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml index a049787b40b9..d8474b865a36 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml @@ -15,15 +15,15 @@ limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - 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"> + 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.systemui.pip.tv.PipControlsView + <com.android.wm.shell.pip.tv.PipControlsView android:id="@+id/pip_controls" android:layout_width="wrap_content" 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 a13e98c0d1ad..227eec2adc87 100644 --- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json +++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json @@ -1,12 +1,24 @@ { "version": "1.0.0", "messages": { + "-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" + }, "-1340279385": { "message": "Remove listener=%s", "level": "VERBOSE", @@ -31,6 +43,12 @@ "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.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", @@ -53,6 +71,9 @@ "groups": { "WM_SHELL_TASK_ORG": { "tag": "WindowManagerShell" + }, + "WM_SHELL_TRANSITIONS": { + "tag": "WindowManagerShell" } } } diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 63b0f6ffbec3..e99350b264b9 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -32,4 +32,8 @@ <!-- Allow one handed to enable round corner --> <bool name="config_one_handed_enable_round_corner">true</bool> + + <!-- 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> </resources> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 7fb641a4b06e..a9917a6b07da 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -57,6 +57,9 @@ <dimen name="pip_resize_handle_margin">4dp</dimen> <dimen name="pip_resize_handle_padding">0dp</dimen> + <dimen name="dismiss_target_x_size">24dp</dimen> + <dimen name="floating_dismiss_bottom_margin">50dp</dimen> + <!-- How high we lift the divider when touching --> <dimen name="docked_stack_divider_lift_elevation">4dp</dimen> 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 9047b71253da..9d6271bca426 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java @@ -22,18 +22,18 @@ import android.util.Slog; import android.view.SurfaceControl; import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.protolog.ShellProtoLogGroup; class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { private static final String TAG = "FullscreenTaskOrg"; - private final TransactionPool mTransactionPool; + private final SyncTransactionQueue mSyncQueue; private final ArraySet<Integer> mTasks = new ArraySet<>(); - FullscreenTaskListener(TransactionPool transactionPool) { - mTransactionPool = transactionPool; + FullscreenTaskListener(SyncTransactionQueue syncQueue) { + mSyncQueue = syncQueue; } @Override @@ -42,18 +42,22 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { if (mTasks.contains(taskInfo.taskId)) { throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId); } - mTasks.add(taskInfo.taskId); - final SurfaceControl.Transaction t = mTransactionPool.acquire(); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d", taskInfo.taskId); - // Reset several properties back to fullscreen (PiP, for example, leaves all these - // properties in a bad state). - t.setPosition(leash, 0, 0); - t.setWindowCrop(leash, null); - t.setAlpha(leash, 1f); - t.setMatrix(leash, 1, 0, 0, 1); - t.show(leash); - t.apply(); + mTasks.add(taskInfo.taskId); + mSyncQueue.runInSync(t -> { + // Reset several properties back to fullscreen (PiP, for example, leaves all these + // properties in a bad state). + t.setPosition(leash, 0, 0); + t.setWindowCrop(leash, null); + // 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); + } + }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 2d82fb1d3a21..8f496d01c83b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -28,6 +28,8 @@ import android.window.TaskOrganizer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -59,16 +61,25 @@ public class ShellTaskOrganizer extends TaskOrganizer { // require us to report to both old and new listeners) private final SparseArray<Pair<RunningTaskInfo, SurfaceControl>> mTasks = new SparseArray<>(); - public ShellTaskOrganizer(TransactionPool transactionPool) { + // TODO(shell-transitions): move to a more "global" Shell location as this isn't only for Tasks + private final Transitions mTransitions; + + public ShellTaskOrganizer(SyncTransactionQueue syncQueue, TransactionPool transactionPool, + ShellExecutor mainExecutor, ShellExecutor animExecutor) { super(); - addListener(new FullscreenTaskListener(transactionPool), WINDOWING_MODE_FULLSCREEN); + addListener(new FullscreenTaskListener(syncQueue), WINDOWING_MODE_FULLSCREEN); + mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor); + if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions); } @VisibleForTesting ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, - TransactionPool transactionPool) { + SyncTransactionQueue syncQueue, TransactionPool transactionPool, + ShellExecutor mainExecutor, ShellExecutor animExecutor) { super(taskOrganizerController); - addListener(new FullscreenTaskListener(transactionPool), WINDOWING_MODE_FULLSCREEN); + addListener(new FullscreenTaskListener(syncQueue), WINDOWING_MODE_FULLSCREEN); + mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor); + if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java new file mode 100644 index 000000000000..36e49d9fd770 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java @@ -0,0 +1,181 @@ +/* + * 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 static android.window.TransitionInfo.TRANSIT_CLOSE; +import static android.window.TransitionInfo.TRANSIT_HIDE; +import static android.window.TransitionInfo.TRANSIT_OPEN; +import static android.window.TransitionInfo.TRANSIT_SHOW; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.os.IBinder; +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.WindowOrganizer; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +import java.util.ArrayList; + +/** Plays transition animations */ +public class Transitions extends ITransitionPlayer.Stub { + private static final String TAG = "ShellTransitions"; + + /** Set to {@code true} to enable shell transitions. */ + public static final boolean ENABLE_SHELL_TRANSITIONS = + SystemProperties.getBoolean("persist.debug.shell_transit", false); + + private final WindowOrganizer mOrganizer; + private final TransactionPool mTransactionPool; + private final ShellExecutor mMainExecutor; + private final ShellExecutor mAnimExecutor; + + /** Keeps track of currently tracked transitions and all the animations associated with each */ + private final ArrayMap<IBinder, ArrayList<Animator>> mActiveTransitions = new ArrayMap<>(); + + Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, + @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { + mOrganizer = organizer; + mTransactionPool = pool; + mMainExecutor = mainExecutor; + mAnimExecutor = animExecutor; + } + + // TODO(shell-transitions): real animations + private void startExampleAnimation(@NonNull IBinder transition, @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); + mMainExecutor.execute(() -> { + mActiveTransitions.get(transition).remove(va); + onFinish(transition); + }); + }; + 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) { } + }); + mActiveTransitions.get(transition).add(va); + mAnimExecutor.execute(va::start); + } + + private static boolean isOpeningType(@WindowManager.TransitionType int legacyType) { + // TODO(shell-transitions): consider providing and using z-order vs the global type for + // this determination. + return legacyType == WindowManager.TRANSIT_TASK_OPEN + || legacyType == WindowManager.TRANSIT_TASK_TO_FRONT + || legacyType == WindowManager.TRANSIT_TASK_OPEN_BEHIND + || legacyType == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; + } + + @Override + public void onTransitionReady(@NonNull IBinder transitionToken, TransitionInfo info, + @NonNull SurfaceControl.Transaction t) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", + transitionToken, info); + // start task + mMainExecutor.execute(() -> { + 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<>()); + for (int i = 0; i < info.getChanges().size(); ++i) { + final SurfaceControl leash = info.getChanges().get(i).getLeash(); + final int mode = info.getChanges().get(i).getMode(); + if (mode == TRANSIT_OPEN || mode == TRANSIT_SHOW) { + t.show(leash); + t.setMatrix(leash, 1, 0, 0, 1); + if (isOpeningType(info.getType())) { + t.setAlpha(leash, 0.f); + startExampleAnimation(transitionToken, leash, true /* show */); + } else { + t.setAlpha(leash, 1.f); + } + } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_HIDE) { + if (!isOpeningType(info.getType())) { + startExampleAnimation(transitionToken, leash, false /* show */); + } + } + } + t.apply(); + onFinish(transitionToken); + }); + } + + @MainThread + private void onFinish(IBinder transition) { + if (!mActiveTransitions.get(transition).isEmpty()) return; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Transition animations finished, notifying core %s", transition); + mActiveTransitions.remove(transition); + mOrganizer.finishTransition(transition, null, null); + } + + @Override + public void requestStartTransition(int type, @NonNull IBinder transitionToken) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s", + type, transitionToken); + mMainExecutor.execute(() -> { + if (mActiveTransitions.containsKey(transitionToken)) { + throw new RuntimeException("Transition already started " + transitionToken); + } + IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */); + mActiveTransitions.put(transition, null); + }); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java index 178d472cfbee..acb9a5dae78c 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellWrapper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.wmshell; +package com.android.wm.shell; import static android.view.Display.DEFAULT_DISPLAY; @@ -22,7 +22,7 @@ import android.app.WindowConfiguration; import android.os.RemoteException; import android.view.WindowManagerGlobal; -import com.android.systemui.shared.system.PinnedStackListenerForwarder; +import com.android.wm.shell.pip.PinnedStackListenerForwarder; /** * The singleton wrapper to communicate between WindowManagerService and WMShell features diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FloatProperties.kt index a284a747da21..d4f82829aa52 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FloatProperties.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.util.animation +package com.android.wm.shell.animation import android.graphics.Rect import android.graphics.RectF diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java index b794b91568fc..416ada739aa3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java @@ -24,6 +24,16 @@ import android.view.animation.PathInterpolator; */ public class Interpolators { /** + * Interpolator for alpha in animation. + */ + public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); + + /** + * Interpolator for alpha out animation. + */ + public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f); + + /** * Interpolator for fast out linear in animation. */ public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt index 2a5424ce4ef7..5cd660a2caa5 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.util.animation +package com.android.wm.shell.animation import android.os.Looper import android.util.ArrayMap @@ -26,7 +26,7 @@ import androidx.dynamicanimation.animation.FlingAnimation import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce -import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance +import com.android.wm.shell.animation.PhysicsAnimator.Companion.getInstance import java.lang.ref.WeakReference import java.util.WeakHashMap import kotlin.math.abs diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt index c50eeac80d7a..86eb8da952f1 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,16 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.util.animation +package com.android.wm.shell.animation import android.os.Handler import android.os.Looper import android.util.ArrayMap import androidx.dynamicanimation.animation.FloatPropertyCompat -import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.prepareForTest -import java.util.ArrayDeque +import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.prepareForTest +import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.collections.HashSet +import kotlin.collections.Set +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.drop +import kotlin.collections.forEach +import kotlin.collections.getOrPut +import kotlin.collections.set +import kotlin.collections.toList +import kotlin.collections.toTypedArray typealias UpdateMatcher = (PhysicsAnimator.AnimationUpdate) -> Boolean typealias UpdateFramesPerProperty<T> = @@ -84,7 +96,7 @@ object PhysicsAnimatorTestUtils { */ @JvmStatic fun setBlockTimeout(timeoutMs: Long) { - this.timeoutMs = timeoutMs + PhysicsAnimatorTestUtils.timeoutMs = timeoutMs } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java new file mode 100644 index 000000000000..96b9f86673fc --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java @@ -0,0 +1,61 @@ +/* + * 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.common; + +import static android.os.Process.THREAD_PRIORITY_DISPLAY; + +import android.annotation.NonNull; +import android.os.HandlerThread; +import android.util.Singleton; + +/** + * A singleton thread for Shell to run animations on. + */ +public class AnimationThread extends HandlerThread { + private ShellExecutor mExecutor; + + private AnimationThread() { + super("wmshell.anim", THREAD_PRIORITY_DISPLAY); + } + + /** Get the singleton instance of this thread */ + public static AnimationThread instance() { + return sAnimationThreadSingleton.get(); + } + + /** + * @return a shared {@link ShellExecutor} associated with this thread + * @hide + */ + @NonNull + public ShellExecutor getExecutor() { + if (mExecutor == null) { + mExecutor = new HandlerExecutor(getThreadHandler()); + } + return mExecutor; + } + + private static final Singleton<AnimationThread> sAnimationThreadSingleton = + new Singleton<AnimationThread>() { + @Override + protected AnimationThread create() { + final AnimationThread animThread = new AnimationThread(); + animThread.start(); + return animThread; + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java index 8946c97a4b58..976fba52b9e2 100644 --- a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.util; +package com.android.wm.shell.common; import android.content.Context; import android.content.res.Configuration; @@ -23,7 +23,7 @@ import android.view.Gravity; import android.widget.FrameLayout; import android.widget.ImageView; -import com.android.systemui.R; +import com.android.wm.shell.R; /** * Circular view with a semitransparent, circular background with an 'X' inside it. 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 c18e9ce76153..d810fb8257a9 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 @@ -43,6 +43,7 @@ import android.view.animation.PathInterpolator; import com.android.internal.view.IInputMethodManager; import java.util.ArrayList; +import java.util.concurrent.Executor; /** * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode. @@ -62,15 +63,21 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged private static final int FLOATING_IME_BOTTOM_INSET = -80; protected final IWindowManager mWmService; - protected final Handler mHandler; + protected final Executor mExecutor; private final TransactionPool mTransactionPool; private final DisplayController mDisplayController; private final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>(); private final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); + @Deprecated public DisplayImeController(IWindowManager wmService, DisplayController displayController, Handler mainHandler, TransactionPool transactionPool) { - mHandler = mainHandler; + this(wmService, displayController, mainHandler::post, transactionPool); + } + + public DisplayImeController(IWindowManager wmService, DisplayController displayController, + Executor mainExecutor, TransactionPool transactionPool) { + mExecutor = mainExecutor; mWmService = wmService; mTransactionPool = transactionPool; mDisplayController = displayController; @@ -197,7 +204,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged @Override public void insetsChanged(InsetsState insetsState) { - mHandler.post(() -> { + mExecutor.execute(() -> { if (mInsetsState.equals(insetsState)) { return; } @@ -224,15 +231,25 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged continue; } if (activeControl.getType() == InsetsState.ITYPE_IME) { - mHandler.post(() -> { + mExecutor.execute(() -> { final Point lastSurfacePosition = mImeSourceControl != null ? mImeSourceControl.getSurfacePosition() : null; + final boolean positionChanged = + !activeControl.getSurfacePosition().equals(lastSurfacePosition); + final boolean leashChanged = + !haveSameLeash(mImeSourceControl, activeControl); mImeSourceControl = activeControl; - if (!activeControl.getSurfacePosition().equals(lastSurfacePosition) - && mAnimation != null) { - startAnimation(mImeShowing, true /* forceRestart */); - } else if (!mImeShowing) { - removeImeSurface(); + if (mAnimation != null) { + if (positionChanged) { + startAnimation(mImeShowing, true /* forceRestart */); + } + } else { + if (leashChanged) { + applyVisibilityToLeash(); + } + if (!mImeShowing) { + removeImeSurface(); + } } }); } @@ -240,13 +257,27 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } } + private void applyVisibilityToLeash() { + SurfaceControl leash = mImeSourceControl.getLeash(); + if (leash != null) { + SurfaceControl.Transaction t = mTransactionPool.acquire(); + if (mImeShowing) { + t.show(leash); + } else { + t.hide(leash); + } + t.apply(); + mTransactionPool.release(t); + } + } + @Override public void showInsets(int types, boolean fromIme) { if ((types & WindowInsets.Type.ime()) == 0) { return; } if (DEBUG) Slog.d(TAG, "Got showInsets for ime"); - mHandler.post(() -> startAnimation(true /* show */, false /* forceRestart */)); + mExecutor.execute(() -> startAnimation(true /* show */, false /* forceRestart */)); } @Override @@ -255,7 +286,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged return; } if (DEBUG) Slog.d(TAG, "Got hideInsets for ime"); - mHandler.post(() -> startAnimation(false /* show */, false /* forceRestart */)); + mExecutor.execute(() -> startAnimation(false /* show */, false /* forceRestart */)); } @Override @@ -495,4 +526,20 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged return IInputMethodManager.Stub.asInterface( ServiceManager.getService(Context.INPUT_METHOD_SERVICE)); } + + private static boolean haveSameLeash(InsetsSourceControl a, InsetsSourceControl b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + if (a.getLeash() == b.getLeash()) { + return true; + } + if (a.getLeash() == null || b.getLeash() == null) { + return false; + } + return a.getLeash().isSameSurface(b.getLeash()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt index bcfb2afeeda1..d5d072a8d449 100644 --- a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt @@ -1,9 +1,24 @@ -package com.android.systemui.util +/* + * 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.common import android.graphics.Rect import android.util.Log -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.util.FloatingContentCoordinator.FloatingContent +import com.android.wm.shell.common.FloatingContentCoordinator.FloatingContent import java.util.HashMap /** Tag for debug logging. */ @@ -20,7 +35,6 @@ private const val TAG = "FloatingCoordinator" * no longer visible. */ -@SysUISingleton class FloatingContentCoordinator constructor() { /** * Represents a piece of floating content, such as PIP or the Bubbles stack. Provides methods @@ -260,14 +274,18 @@ class FloatingContentCoordinator constructor() { // Lazily calculate the closest possible new tops for the content, above and below its // current location. - val newContentBoundsAbove by lazy { findAreaForContentAboveOrBelow( - contentRect, - exclusionRects = rectsToAvoidAbove.plus(newlyOverlappingRect), - findAbove = true) } - val newContentBoundsBelow by lazy { findAreaForContentAboveOrBelow( - contentRect, - exclusionRects = rectsToAvoidBelow.plus(newlyOverlappingRect), - findAbove = false) } + val newContentBoundsAbove by lazy { + findAreaForContentAboveOrBelow( + contentRect, + exclusionRects = rectsToAvoidAbove.plus(newlyOverlappingRect), + findAbove = true) + } + val newContentBoundsBelow by lazy { + findAreaForContentAboveOrBelow( + contentRect, + exclusionRects = rectsToAvoidBelow.plus(newlyOverlappingRect), + findAbove = false) + } val positionAboveInBounds by lazy { allowedBounds.contains(newContentBoundsAbove) } val positionBelowInBounds by lazy { allowedBounds.contains(newContentBoundsBelow) } @@ -347,4 +365,4 @@ class FloatingContentCoordinator constructor() { (r1.right <= r2.right && r1.right >= r2.left) } } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java new file mode 100644 index 000000000000..cd75840b8c71 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java @@ -0,0 +1,48 @@ +/* + * 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.common; + +import android.annotation.NonNull; +import android.os.Handler; + +/** Executor implementation which is backed by a Handler. */ +public class HandlerExecutor implements ShellExecutor { + private final Handler mHandler; + + public HandlerExecutor(@NonNull Handler handler) { + mHandler = handler; + } + + @Override + public void executeDelayed(@NonNull Runnable r, long delayMillis) { + if (!mHandler.postDelayed(r, delayMillis)) { + throw new RuntimeException(mHandler + " is probably exiting"); + } + } + + @Override + public void removeCallbacks(@NonNull Runnable r) { + mHandler.removeCallbacks(r); + } + + @Override + public void execute(@NonNull Runnable command) { + if (!mHandler.post(command)) { + throw new RuntimeException(mHandler + " is probably exiting"); + } + } +} diff --git a/libs/hwui/shader/BitmapShader.h b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java index ed6a6e6802d1..aafe2407a1ea 100644 --- a/libs/hwui/shader/BitmapShader.h +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java @@ -14,26 +14,22 @@ * limitations under the License. */ -#pragma once +package com.android.wm.shell.common; -#include "Shader.h" -#include "SkShader.h" - -namespace android::uirenderer { +import java.util.concurrent.Executor; /** - * Shader implementation that renders a Bitmap as either a SkShader or SkImageFilter + * Super basic Executor interface that adds support for delayed execution and removing callbacks. + * Intended to wrap Handler while better-supporting testing. */ -class BitmapShader : public Shader { -public: - BitmapShader(const sk_sp<SkImage>& image, const SkTileMode tileModeX, - const SkTileMode tileModeY, const SkMatrix* matrix); - ~BitmapShader() override; - -protected: - sk_sp<SkShader> makeSkShader() override; +public interface ShellExecutor extends Executor { + /** + * See {@link android.os.Handler#postDelayed(Runnable, long)}. + */ + void executeDelayed(Runnable r, long delayMillis); -private: - sk_sp<SkShader> skShader; -}; -} // namespace android::uirenderer
\ No newline at end of file + /** + * See {@link android.os.Handler#removeCallbacks}. + */ + void removeCallbacks(Runnable r); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index b4620e27e68c..84b98f9e8047 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -271,14 +271,14 @@ public class SystemWindows { } @Override - public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs, + public int relayout(IWindow window, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, long frameNumber, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls, Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { - int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight, + int res = super.relayout(window, attrs, requestedWidth, requestedHeight, viewVisibility, flags, frameNumber, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState, outActiveControls, outSurfaceSize, outBLASTSurfaceControl); @@ -365,10 +365,6 @@ public class SystemWindows { public void updatePointerIcon(float x, float y) {} @Override - public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, - int localValue, int localChanges) {} - - @Override public void dispatchWindowShown() {} @Override diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt index f441049feefb..b4d738712893 100644 --- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.util.magnetictarget +package com.android.wm.shell.common.magnetictarget import android.annotation.SuppressLint import android.content.Context @@ -31,7 +31,7 @@ import android.view.ViewConfiguration import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringForce -import com.android.systemui.util.animation.PhysicsAnimator +import com.android.wm.shell.animation.PhysicsAnimator import kotlin.math.abs import kotlin.math.hypot 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 9954618134e8..6bc838fcc8be 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 @@ -239,9 +239,14 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { throw new RuntimeException("Callers should call scheduleOffset() instead of this " + "directly"); } + final WindowContainerTransaction wct = new WindowContainerTransaction(); mDisplayAreaMap.forEach( - (key, leash) -> animateWindows(leash, fromBounds, toBounds, direction, - durationMs)); + (key, leash) -> { + animateWindows(leash, fromBounds, toBounds, direction, + durationMs); + wct.setBounds(key.token, toBounds); + }); + applyTransaction(wct); } private void resetWindowsOffset() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java index 7c0c738644b7..b6b518d69c55 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java @@ -184,9 +184,8 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback { mDisplaySize.x, mTutorialAreaHeight, 0, 0, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSLUCENT); + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); lp.gravity = Gravity.TOP | Gravity.LEFT; lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; lp.setFitInsetsTypes(0 /* types */); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java index 2091baaaf8a1..993e0e7ed016 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.shared.system; +package com.android.wm.shell.pip; import android.app.RemoteAction; import android.content.ComponentName; diff --git a/packages/SystemUI/src/com/android/systemui/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index 2b115508e525..7c2625133cad 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 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,14 +14,15 @@ * limitations under the License. */ -package com.android.systemui.pip; +package com.android.wm.shell.pip; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.ComponentName; import android.media.session.MediaController; -import com.android.systemui.pip.phone.PipTouchHandler; -import com.android.systemui.pip.tv.PipController; +import com.android.wm.shell.pip.phone.PipTouchHandler; +import com.android.wm.shell.pip.tv.PipController; import java.io.PrintWriter; import java.util.function.Consumer; @@ -31,14 +32,14 @@ import java.util.function.Consumer; */ public interface Pip { /** - * Registers {@link com.android.systemui.pip.tv.PipController.Listener} that gets called. + * Registers {@link com.android.wm.shell.pip.tv.PipController.Listener} that gets called. * whenever receiving notification on changes in PIP. */ default void addListener(PipController.Listener listener) { } /** - * Registers a {@link com.android.systemui.pip.tv.PipController.MediaListener} to PipController. + * Registers a {@link PipController.MediaListener} to PipController. */ default void addMediaListener(PipController.MediaListener listener) { } @@ -72,7 +73,13 @@ public interface Pip { return -1; } - default PipTouchHandler getPipTouchHandler() { + /** + * Get the touch handler which manages all the touch handling for PIP on the Phone, + * including moving, dismissing and expanding the PIP. (Do not used in TV) + * + * @return + */ + default @Nullable PipTouchHandler getPipTouchHandler() { return null; } @@ -167,7 +174,7 @@ public interface Pip { } /** - * Removes a {@link com.android.systemui.pip.tv.PipController.MediaListener} from PipController. + * Removes a {@link PipController.MediaListener} from PipController. */ default void removeMediaListener(PipController.MediaListener listener) { } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 52ce7fe210ec..d82946269ee8 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip; +package com.android.wm.shell.pip; import android.animation.AnimationHandler; import android.animation.Animator; @@ -26,7 +26,7 @@ import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; -import com.android.systemui.Interpolators; +import com.android.wm.shell.animation.Interpolators; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -86,12 +86,13 @@ public class PipAnimationController { return handler; }); - PipAnimationController(PipSurfaceTransactionHelper helper) { + public PipAnimationController(PipSurfaceTransactionHelper helper) { mSurfaceTransactionHelper = helper; } @SuppressWarnings("unchecked") - PipTransitionAnimator getAnimator(SurfaceControl leash, + @VisibleForTesting + public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect destinationBounds, float alphaStart, float alphaEnd) { if (mCurrentAnimator == null) { mCurrentAnimator = setupPipTransitionAnimator( @@ -108,7 +109,8 @@ public class PipAnimationController { } @SuppressWarnings("unchecked") - PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds, + @VisibleForTesting + public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction) { if (mCurrentAnimator == null) { mCurrentAnimator = setupPipTransitionAnimator( @@ -234,20 +236,23 @@ public class PipAnimationController { @Override public void onAnimationRepeat(Animator animation) {} - @AnimationType int getAnimationType() { + @VisibleForTesting + @AnimationType public int getAnimationType() { return mAnimationType; } - PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) { + @VisibleForTesting + public PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) { mPipAnimationCallback = callback; return this; } - - @TransitionDirection int getTransitionDirection() { + @VisibleForTesting + @TransitionDirection public int getTransitionDirection() { return mTransitionDirection; } - PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) { + @VisibleForTesting + public PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) { if (direction != TRANSITION_DIRECTION_SAME) { mTransitionDirection = direction; } @@ -258,7 +263,8 @@ public class PipAnimationController { return mStartValue; } - T getEndValue() { + @VisibleForTesting + public T getEndValue() { return mEndValue; } @@ -295,7 +301,7 @@ public class PipAnimationController { * animation. In which case we can update the end bounds and keep the existing animation * running instead of cancelling it. */ - void updateEndValue(T endValue) { + public void updateEndValue(T endValue) { mEndValue = endValue; } @@ -304,7 +310,7 @@ public class PipAnimationController { } @VisibleForTesting - void setSurfaceControlTransactionFactory( + public void setSurfaceControlTransactionFactory( PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { mSurfaceControlTransactionFactory = factory; } @@ -353,7 +359,7 @@ public class PipAnimationController { } @Override - void updateEndValue(Float endValue) { + public void updateEndValue(Float endValue) { super.updateEndValue(endValue); mStartValue = mCurrentValue; } @@ -444,7 +450,7 @@ public class PipAnimationController { } @Override - void updateEndValue(Rect endValue) { + public void updateEndValue(Rect endValue) { super.updateEndValue(endValue); if (mStartValue != null && mCurrentValue != null) { mStartValue.set(mCurrentValue); diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsHandler.java index 89b5c38d94a7..de3261baf9b7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip; +package com.android.wm.shell.pip; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; @@ -39,7 +39,6 @@ import android.view.DisplayInfo; import android.view.Gravity; import android.window.WindowContainerTransaction; -import com.android.systemui.dagger.SysUISingleton; import com.android.wm.shell.common.DisplayLayout; import java.io.PrintWriter; @@ -48,7 +47,6 @@ import java.io.PrintWriter; * Handles bounds calculation for PIP on Phone and other form factors, it keeps tracking variant * state changes originated from Window Manager and is the source of truth for PiP window bounds. */ -@SysUISingleton public class PipBoundsHandler { private static final String TAG = PipBoundsHandler.class.getSimpleName(); @@ -254,7 +252,7 @@ public class PipBoundsHandler { /** * See {@link #getDestinationBounds(ComponentName, float, Rect, Size, boolean)} */ - Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds, + public Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds, Size minimalSize) { return getDestinationBounds(componentName, aspectRatio, bounds, minimalSize, false /* useCurrentMinEdgeSize */); @@ -263,7 +261,7 @@ public class PipBoundsHandler { /** * @return {@link Rect} of the destination PiP window bounds. */ - Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds, + public Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds, Size minimalSize, boolean useCurrentMinEdgeSize) { if (!componentName.equals(mLastPipComponentName)) { onResetReentryBoundsUnchecked(); @@ -288,7 +286,7 @@ public class PipBoundsHandler { return destinationBounds; } - float getDefaultAspectRatio() { + public float getDefaultAspectRatio() { return mDefaultAspectRatio; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java index 5d23e4207c33..820930c463f2 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip; +package com.android.wm.shell.pip; import android.content.Context; import android.content.res.Resources; diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java index fc724cb539dc..b9a5536de743 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip; +package com.android.wm.shell.pip; import android.content.Context; import android.content.res.Resources; @@ -23,13 +23,11 @@ import android.graphics.Rect; import android.graphics.RectF; import android.view.SurfaceControl; -import com.android.systemui.dagger.SysUISingleton; import com.android.wm.shell.R; /** * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition. */ -@SysUISingleton public class PipSurfaceTransactionHelper { private final boolean mEnableCornerRadius; @@ -63,7 +61,7 @@ public class PipSurfaceTransactionHelper { * Operates the alpha on a given transaction and leash * @return same {@link PipSurfaceTransactionHelper} instance for method chaining */ - PipSurfaceTransactionHelper alpha(SurfaceControl.Transaction tx, SurfaceControl leash, + public PipSurfaceTransactionHelper alpha(SurfaceControl.Transaction tx, SurfaceControl leash, float alpha) { tx.setAlpha(leash, alpha); return this; @@ -73,7 +71,7 @@ public class PipSurfaceTransactionHelper { * Operates the crop (and position) on a given transaction and leash * @return same {@link PipSurfaceTransactionHelper} instance for method chaining */ - PipSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash, + public PipSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash, Rect destinationBounds) { tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height()) .setPosition(leash, destinationBounds.left, destinationBounds.top); @@ -84,7 +82,7 @@ public class PipSurfaceTransactionHelper { * Operates the scale (setMatrix) on a given transaction and leash * @return same {@link PipSurfaceTransactionHelper} instance for method chaining */ - PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, + public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds) { mTmpSourceRectF.set(sourceBounds); mTmpDestinationRectF.set(destinationBounds); @@ -98,7 +96,8 @@ public class PipSurfaceTransactionHelper { * Operates the scale (setMatrix) on a given transaction and leash * @return same {@link PipSurfaceTransactionHelper} instance for method chaining */ - PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash, + public PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx, + SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets) { mTmpSourceRectF.set(sourceBounds); mTmpDestinationRect.set(sourceBounds); @@ -119,9 +118,11 @@ public class PipSurfaceTransactionHelper { /** * Resets the scale (setMatrix) on a given transaction and leash if there's any + * * @return same {@link PipSurfaceTransactionHelper} instance for method chaining */ - PipSurfaceTransactionHelper resetScale(SurfaceControl.Transaction tx, SurfaceControl leash, + public PipSurfaceTransactionHelper resetScale(SurfaceControl.Transaction tx, + SurfaceControl leash, Rect destinationBounds) { tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9) .setPosition(leash, destinationBounds.left, destinationBounds.top); @@ -132,7 +133,7 @@ public class PipSurfaceTransactionHelper { * Operates the round corner radius on a given transaction and leash * @return same {@link PipSurfaceTransactionHelper} instance for method chaining */ - PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash, + public PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash, boolean applyCornerRadius) { if (mEnableCornerRadius) { tx.setCornerRadius(leash, applyCornerRadius ? mCornerRadius : 0); @@ -140,7 +141,7 @@ public class PipSurfaceTransactionHelper { return this; } - interface SurfaceControlTransactionFactory { + public interface SurfaceControlTransactionFactory { SurfaceControl.Transaction getTransaction(); } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index fb86535b4e90..bb501fb050e6 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -14,23 +14,23 @@ * limitations under the License. */ -package com.android.systemui.pip; +package com.android.wm.shell.pip; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_ALPHA; -import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_BOUNDS; -import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; -import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN; -import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_NONE; -import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK; -import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_SAME; -import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; -import static com.android.systemui.pip.PipAnimationController.isInPipDirection; -import static com.android.systemui.pip.PipAnimationController.isOutPipDirection; +import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; +import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_NONE; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; +import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; +import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; import android.annotation.NonNull; import android.annotation.Nullable; @@ -59,13 +59,13 @@ import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; import com.android.internal.os.SomeArgs; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.pip.phone.PipMenuActivityController; -import com.android.systemui.pip.phone.PipUpdateThread; -import com.android.systemui.pip.phone.PipUtils; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.pip.phone.PipMenuActivityController; +import com.android.wm.shell.pip.phone.PipMotionHelper; +import com.android.wm.shell.pip.phone.PipUpdateThread; +import com.android.wm.shell.pip.phone.PipUtils; import com.android.wm.shell.splitscreen.SplitScreen; import java.io.PrintWriter; @@ -86,9 +86,8 @@ import java.util.function.Consumer; * and files a final {@link WindowContainerTransaction} at the end of the transition. * * This class is also responsible for general resize/offset PiP operations within SysUI component, - * see also {@link com.android.systemui.pip.phone.PipMotionHelper}. + * see also {@link PipMotionHelper}. */ -@SysUISingleton public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganizer.TaskListener, DisplayController.OnDisplaysChangedListener { private static final String TAG = PipTaskOrganizer.class.getSimpleName(); @@ -262,10 +261,10 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize if (!PipUtils.hasSystemFeature(context)) { Log.w(TAG, "Device not support PIP feature"); - return; + } else { + mTaskOrganizer.addListener(this, WINDOWING_MODE_PINNED); + displayController.addDisplayWindowListener(this); } - mTaskOrganizer.addListener(this, WINDOWING_MODE_PINNED); - displayController.addDisplayWindowListener(this); } public Handler getUpdateHandler() { @@ -325,9 +324,13 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize return; } + final Configuration initialConfig = mInitialState.remove(mToken.asBinder()); + if (initialConfig == null) { + Log.wtf(TAG, "Token not in record, this should not happen mToken=" + mToken); + return; + } mPipUiEventLoggerLogger.log( PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN); - final Configuration initialConfig = mInitialState.remove(mToken.asBinder()); final boolean orientationDiffers = initialConfig.windowConfiguration.getRotation() != mPipBoundsHandler.getDisplayRotation(); final WindowContainerTransaction wct = new WindowContainerTransaction(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java index 22adbb77d70a..de3bb2950c0a 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java @@ -14,19 +14,17 @@ * limitations under the License. */ -package com.android.systemui.pip; +package com.android.wm.shell.pip; import android.app.TaskInfo; import android.content.pm.PackageManager; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; -import com.android.systemui.dagger.SysUISingleton; /** * Helper class that ends PiP log to UiEvent, see also go/uievent */ -@SysUISingleton public class PipUiEventLogger { private static final int INVALID_PACKAGE_UID = -1; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java index a13318990f40..fddd5472e02c 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 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,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import android.content.Context; import android.graphics.Rect; @@ -27,9 +27,9 @@ import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; -import com.android.systemui.pip.PipSnapAlgorithm; -import com.android.systemui.pip.PipTaskOrganizer; import com.android.wm.shell.R; +import com.android.wm.shell.pip.PipSnapAlgorithm; +import com.android.wm.shell.pip.PipTaskOrganizer; import java.util.ArrayList; import java.util.List; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java index 7dfd99c2110d..6b6b5211b10a 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 625304371d5b..5193656e8299 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 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,11 +14,11 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static com.android.systemui.pip.PipAnimationController.isOutPipDirection; +import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; import android.annotation.Nullable; import android.app.ActivityManager; @@ -36,14 +36,13 @@ import android.view.DisplayInfo; import android.view.IPinnedStackController; import android.window.WindowContainerTransaction; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.pip.Pip; -import com.android.systemui.pip.PipBoundsHandler; -import com.android.systemui.pip.PipTaskOrganizer; -import com.android.systemui.shared.system.PinnedStackListenerForwarder; -import com.android.systemui.wmshell.WindowManagerShellWrapper; +import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.pip.PinnedStackListenerForwarder; +import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.pip.PipBoundsHandler; +import com.android.wm.shell.pip.PipTaskOrganizer; import java.io.PrintWriter; import java.util.function.Consumer; @@ -51,7 +50,6 @@ import java.util.function.Consumer; /** * Manages the picture-in-picture (PIP) UI and states for Phones. */ -@SysUISingleton public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback { private static final String TAG = "PipController"; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMediaController.java index a5b5092ead8c..4a8db6b42b3f 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMediaController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; @@ -33,7 +33,7 @@ import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.UserHandle; -import com.android.systemui.R; +import com.android.wm.shell.R; import java.util.ArrayList; import java.util.Collections; @@ -46,10 +46,10 @@ import java.util.List; */ public class PipMediaController { - private static final String ACTION_PLAY = "com.android.systemui.pip.phone.PLAY"; - private static final String ACTION_PAUSE = "com.android.systemui.pip.phone.PAUSE"; - private static final String ACTION_NEXT = "com.android.systemui.pip.phone.NEXT"; - private static final String ACTION_PREV = "com.android.systemui.pip.phone.PREV"; + private static final String ACTION_PLAY = "com.android.wm.shell.pip.phone.PLAY"; + private static final String ACTION_PAUSE = "com.android.wm.shell.pip.phone.PAUSE"; + private static final String ACTION_NEXT = "com.android.wm.shell.pip.phone.NEXT"; + private static final String ACTION_PREV = "com.android.wm.shell.pip.phone.PREV"; /** * A listener interface to receive notification on changes to the media actions. @@ -88,12 +88,13 @@ public class PipMediaController { } }; - private final MediaController.Callback mPlaybackChangedListener = new MediaController.Callback() { - @Override - public void onPlaybackStateChanged(PlaybackState state) { - notifyActionsChanged(); - } - }; + private final MediaController.Callback mPlaybackChangedListener = + new MediaController.Callback() { + @Override + public void onPlaybackStateChanged(PlaybackState state) { + notifyActionsChanged(); + } + }; private final MediaSessionManager.OnActiveSessionsChangedListener mSessionsChangedListener = controllers -> resolveActiveMediaController(controllers); @@ -180,26 +181,26 @@ public class PipMediaController { String pauseDescription = mContext.getString(R.string.pip_pause); mPauseAction = new RemoteAction(Icon.createWithResource(mContext, R.drawable.pip_ic_pause_white), pauseDescription, pauseDescription, - PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PAUSE), - FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PAUSE), + FLAG_UPDATE_CURRENT)); String playDescription = mContext.getString(R.string.pip_play); mPlayAction = new RemoteAction(Icon.createWithResource(mContext, R.drawable.pip_ic_play_arrow_white), playDescription, playDescription, - PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PLAY), - FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PLAY), + FLAG_UPDATE_CURRENT)); String nextDescription = mContext.getString(R.string.pip_skip_to_next); mNextAction = new RemoteAction(Icon.createWithResource(mContext, - R.drawable.ic_skip_next_white), nextDescription, nextDescription, - PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_NEXT), - FLAG_UPDATE_CURRENT)); + R.drawable.pip_ic_skip_next_white), nextDescription, nextDescription, + PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_NEXT), + FLAG_UPDATE_CURRENT)); String prevDescription = mContext.getString(R.string.pip_skip_to_prev); mPrevAction = new RemoteAction(Icon.createWithResource(mContext, - R.drawable.ic_skip_previous_white), prevDescription, prevDescription, - PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PREV), - FLAG_UPDATE_CURRENT)); + R.drawable.pip_ic_skip_previous_white), prevDescription, prevDescription, + PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PREV), + FLAG_UPDATE_CURRENT)); } /** diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActivityController.java index 6c232251e817..c53803a7f8cc 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActivityController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; @@ -34,8 +34,8 @@ import android.view.MotionEvent; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import com.android.systemui.pip.PipTaskOrganizer; -import com.android.systemui.pip.phone.PipMediaController.ActionListener; +import com.android.wm.shell.pip.PipTaskOrganizer; +import com.android.wm.shell.pip.phone.PipMediaController.ActionListener; import java.io.PrintWriter; import java.util.ArrayList; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java index 6cfed070198b..985cd0f1fa19 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import android.content.Context; import android.graphics.Rect; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 48ddbffa2d39..24e49f8d9821 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -23,9 +23,9 @@ import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTR import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; -import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE; -import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL; -import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE; +import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE; +import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_FULL; +import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_NONE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -59,8 +59,8 @@ import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; -import com.android.systemui.Interpolators; -import com.android.systemui.R; +import com.android.wm.shell.R; +import com.android.wm.shell.animation.Interpolators; import java.util.ArrayList; import java.util.List; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index e24121928808..cc86cf97104b 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,16 +28,17 @@ import android.os.Looper; import android.util.Log; import android.view.Choreographer; +import androidx.annotation.VisibleForTesting; import androidx.dynamicanimation.animation.AnimationHandler; import androidx.dynamicanimation.animation.AnimationHandler.FrameCallbackScheduler; import androidx.dynamicanimation.animation.SpringForce; -import com.android.systemui.pip.PipSnapAlgorithm; -import com.android.systemui.pip.PipTaskOrganizer; -import com.android.systemui.util.FloatingContentCoordinator; -import com.android.systemui.util.animation.FloatProperties; -import com.android.systemui.util.animation.PhysicsAnimator; -import com.android.systemui.util.magnetictarget.MagnetizedObject; +import com.android.wm.shell.animation.FloatProperties; +import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.magnetictarget.MagnetizedObject; +import com.android.wm.shell.pip.PipSnapAlgorithm; +import com.android.wm.shell.pip.PipTaskOrganizer; import java.io.PrintWriter; import java.util.function.Consumer; @@ -503,7 +504,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** * Animates the PiP to offset it from the IME or shelf. */ - void animateToOffset(Rect originalBounds, int offset) { + @VisibleForTesting + public void animateToOffset(Rect originalBounds, int offset) { if (DEBUG) { Log.d(TAG, "animateToOffset: originalBounds=" + originalBounds + " offset=" + offset + " callers=\n" + Debug.getCallers(5, " ")); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 1e3d871713c7..ef3875597aa2 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_PINCH_RESIZE; import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; @@ -42,11 +42,13 @@ import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.ViewConfiguration; +import androidx.annotation.VisibleForTesting; + import com.android.internal.policy.TaskResizingAlgorithm; -import com.android.systemui.pip.PipBoundsHandler; -import com.android.systemui.pip.PipTaskOrganizer; -import com.android.systemui.pip.PipUiEventLogger; import com.android.wm.shell.R; +import com.android.wm.shell.pip.PipBoundsHandler; +import com.android.wm.shell.pip.PipTaskOrganizer; +import com.android.wm.shell.pip.PipUiEventLogger; import java.io.PrintWriter; import java.util.concurrent.Executor; @@ -490,11 +492,11 @@ public class PipResizeGestureHandler { return mUserResizeBounds; } - void updateMaxSize(int maxX, int maxY) { + @VisibleForTesting public void updateMaxSize(int maxX, int maxY) { mMaxSize.set(maxX, maxY); } - void updateMinSize(int minX, int minY) { + @VisibleForTesting public void updateMinSize(int minX, int minY) { mMinSize.set(minX, minY); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java index 72335dbed115..1a3cc8b1c1d2 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; /** * A generic interface for a touch gesture. diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 1f9125da4d48..6b3177225a35 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING; -import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; -import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE; -import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL; -import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; +import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE; +import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_FULL; +import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_NONE; import android.annotation.SuppressLint; import android.content.ComponentName; @@ -55,15 +55,15 @@ import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.R; -import com.android.systemui.pip.PipAnimationController; -import com.android.systemui.pip.PipBoundsHandler; -import com.android.systemui.pip.PipTaskOrganizer; -import com.android.systemui.pip.PipUiEventLogger; -import com.android.systemui.util.DismissCircleView; -import com.android.systemui.util.FloatingContentCoordinator; -import com.android.systemui.util.animation.PhysicsAnimator; -import com.android.systemui.util.magnetictarget.MagnetizedObject; +import com.android.wm.shell.R; +import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.common.DismissCircleView; +import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.magnetictarget.MagnetizedObject; +import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.pip.PipBoundsHandler; +import com.android.wm.shell.pip.PipTaskOrganizer; +import com.android.wm.shell.pip.PipUiEventLogger; import java.io.PrintWriter; @@ -140,9 +140,9 @@ public class PipTouchHandler { private Rect mInsetBounds = new Rect(); // The reference bounds used to calculate the normal/expanded target bounds private Rect mNormalBounds = new Rect(); - @VisibleForTesting Rect mNormalMovementBounds = new Rect(); + @VisibleForTesting public Rect mNormalMovementBounds = new Rect(); private Rect mExpandedBounds = new Rect(); - @VisibleForTesting Rect mExpandedMovementBounds = new Rect(); + @VisibleForTesting public Rect mExpandedMovementBounds = new Rect(); private int mExpandedShortestEdgeSize; // Used to workaround an issue where the WM rotation happens before we are notified, allowing @@ -898,17 +898,17 @@ public class PipTouchHandler { } @VisibleForTesting - PipResizeGestureHandler getPipResizeGestureHandler() { + public PipResizeGestureHandler getPipResizeGestureHandler() { return mPipResizeGestureHandler; } @VisibleForTesting - void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) { + public void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) { mPipResizeGestureHandler = pipResizeGestureHandler; } @VisibleForTesting - void setPipMotionHelper(PipMotionHelper pipMotionHelper) { + public void setPipMotionHelper(PipMotionHelper pipMotionHelper) { mMotionHelper = pipMotionHelper; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java index ecd1128a5680..217150770084 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import android.graphics.PointF; import android.os.Handler; @@ -35,7 +35,7 @@ public class PipTouchState { private static final boolean DEBUG = false; @VisibleForTesting - static final long DOUBLE_TAP_TIMEOUT = 200; + public static final long DOUBLE_TAP_TIMEOUT = 200; static final long HOVER_EXIT_TIMEOUT = 50; private final Handler mHandler; @@ -106,8 +106,8 @@ public class PipTouchState { mAllowDraggingOffscreen = true; mIsUserInteracting = true; mDownTouchTime = ev.getEventTime(); - mIsDoubleTap = !mPreviouslyDragging && - (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT; + mIsDoubleTap = !mPreviouslyDragging + && (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT; mIsWaitingForDoubleTap = false; mIsDragging = false; mLastDownTouchTime = mDownTouchTime; @@ -163,8 +163,8 @@ public class PipTouchState { final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; mActivePointerId = ev.getPointerId(newPointerIndex); if (DEBUG) { - Log.e(TAG, "Relinquish active pointer id on POINTER_UP: " + - mActivePointerId); + Log.e(TAG, + "Relinquish active pointer id on POINTER_UP: " + mActivePointerId); } mLastTouch.set(ev.getRawX(newPointerIndex), ev.getRawY(newPointerIndex)); } @@ -191,8 +191,8 @@ public class PipTouchState { mUpTouchTime = ev.getEventTime(); mLastTouch.set(ev.getRawX(pointerIndex), ev.getRawY(pointerIndex)); mPreviouslyDragging = mIsDragging; - mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging && - (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT; + mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging + && (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT; // Fall through to clean up } @@ -223,7 +223,7 @@ public class PipTouchState { /** * @return the movement delta between the last handled touch event and the previous touch - * position. + * position. */ public PointF getLastTouchDelta() { return mLastDelta; @@ -238,7 +238,7 @@ public class PipTouchState { /** * @return the movement delta between the last handled touch event and the down touch - * position. + * position. */ public PointF getDownTouchDelta() { return mDownDelta; @@ -318,7 +318,8 @@ public class PipTouchState { } } - @VisibleForTesting long getDoubleTapTimeoutCallbackDelay() { + @VisibleForTesting + public long getDoubleTapTimeoutCallbackDelay() { if (mIsWaitingForDoubleTap) { return Math.max(0, DOUBLE_TAP_TIMEOUT - (mUpTouchTime - mDownTouchTime)); } @@ -333,7 +334,8 @@ public class PipTouchState { mHandler.removeCallbacks(mDoubleTapTimeoutCallback); } - void scheduleHoverExitTimeoutCallback() { + @VisibleForTesting + public void scheduleHoverExitTimeoutCallback() { mHandler.removeCallbacks(mHoverExitTimeoutCallback); mHandler.postDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUpdateThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java index 6c5d84645e92..d686cac3457b 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUpdateThread.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import android.os.Handler; import android.os.HandlerThread; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUtils.java index 1bf6dd7bb9ef..6a58ce0d4646 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.wm.shell.pip.phone; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; @@ -35,7 +35,7 @@ public class PipUtils { /** * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. - * The component name may be null if no such activity exists. + * The component name may be null if no such activity exists. */ public static Pair<ComponentName, Integer> getTopPipActivity(Context context, IActivityManager activityManager) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlButtonView.java index db9bedd2e620..4e82bb557fb9 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlButtonView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.tv; +package com.android.wm.shell.pip.tv; import android.animation.Animator; import android.animation.AnimatorInflater; @@ -83,9 +83,9 @@ public class PipControlButtonView extends RelativeLayout { 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, defStyleRes); + int[] values = new int[]{android.R.attr.src, android.R.attr.text}; + TypedArray typedArray = context.obtainStyledAttributes(attrs, values, defStyleAttr, + defStyleRes); setImageResource(typedArray.getResourceId(0, 0)); setText(typedArray.getResourceId(1, 0)); diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java index 6ac4e4cbd80d..3eec20f53d24 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.tv; +package com.android.wm.shell.pip.tv; import static android.app.ActivityTaskManager.INVALID_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; @@ -45,13 +45,12 @@ import android.text.TextUtils; import android.util.Log; import android.view.DisplayInfo; -import com.android.systemui.R; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.pip.Pip; -import com.android.systemui.pip.PipBoundsHandler; -import com.android.systemui.pip.PipTaskOrganizer; -import com.android.systemui.shared.system.PinnedStackListenerForwarder; -import com.android.systemui.wmshell.WindowManagerShellWrapper; +import com.android.wm.shell.R; +import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.pip.PinnedStackListenerForwarder; +import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.pip.PipBoundsHandler; +import com.android.wm.shell.pip.PipTaskOrganizer; import java.util.ArrayList; import java.util.List; @@ -59,7 +58,6 @@ import java.util.List; /** * Manages the picture-in-picture (PIP) UI and states. */ -@SysUISingleton public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback { private static final String TAG = "PipController"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -219,42 +217,42 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac PipTaskOrganizer pipTaskOrganizer, WindowManagerShellWrapper windowManagerShellWrapper ) { - if (mInitialized) { - return; + if (!mInitialized) { + mInitialized = true; + mContext = context; + mPipNotification = new PipNotification(context, this); + mPipBoundsHandler = pipBoundsHandler; + // 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); + mPipBoundsHandler.onDisplayInfoChanged(displayInfo); + + mResizeAnimationDuration = context.getResources() + .getInteger(R.integer.config_pipResizeAnimationDuration); + mPipTaskOrganizer = pipTaskOrganizer; + mPipTaskOrganizer.registerPipTransitionCallback(this); + mActivityTaskManager = ActivityTaskManager.getService(); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.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); + + mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); + mWindowManagerShellWrapper = windowManagerShellWrapper; + try { + mWindowManagerShellWrapper.addPinnedStackListener(mPinnedStackListener); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register pinned stack listener", e); + } } - mInitialized = true; - mContext = context; - mPipNotification = new PipNotification(context, this); - mPipBoundsHandler = pipBoundsHandler; - // 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); - mPipBoundsHandler.onDisplayInfoChanged(displayInfo); - - mResizeAnimationDuration = context.getResources() - .getInteger(R.integer.config_pipResizeAnimationDuration); - mPipTaskOrganizer = pipTaskOrganizer; - mPipTaskOrganizer.registerPipTransitionCallback(this); - mActivityTaskManager = ActivityTaskManager.getService(); - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.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); - - mMediaSessionManager = - (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); - mWindowManagerShellWrapper = windowManagerShellWrapper; - try { - mWindowManagerShellWrapper.addPinnedStackListener(mPinnedStackListener); - } catch (RemoteException e) { - Log.e(TAG, "Failed to register pinned stack listener", e); - } + // TODO(b/169395392) Refactor PipMenuActivity to PipMenuView + PipMenuActivity.setPipController(this); } private void loadConfigurationsAndApply(Configuration newConfig) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsView.java index 125444d2dfb5..14960c38fd43 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.tv; +package com.android.wm.shell.pip.tv; import android.content.Context; import android.util.AttributeSet; @@ -30,6 +30,10 @@ import com.android.wm.shell.R; */ public class PipControlsView extends LinearLayout { + public PipControlsView(Context context) { + this(context, null); + } + public PipControlsView(Context context, AttributeSet attrs) { this(context, attrs, 0); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsViewController.java index 8c04a52987c4..f66e9025a9ed 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsViewController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.tv; +package com.android.wm.shell.pip.tv; import android.app.PendingIntent; import android.app.RemoteAction; @@ -26,17 +26,13 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.pip.Pip; import com.android.wm.shell.R; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import javax.inject.Inject; /** * Controller for {@link PipControlsView}. @@ -49,7 +45,7 @@ public class PipControlsViewController { private final PipControlsView mView; private final LayoutInflater mLayoutInflater; private final Handler mHandler; - private final Optional<Pip> mPipOptional; + private final PipController mPipController; private final PipControlButtonView mPlayPauseButtonView; private MediaController mMediaController; private PipControlButtonView mFocusedChild; @@ -77,14 +73,12 @@ public class PipControlsViewController { @Override public void onViewAttachedToWindow(View v) { updateMediaController(); - mPipOptional.ifPresent( - pip -> pip.addMediaListener(mPipMediaListener)); + mPipController.addMediaListener(mPipMediaListener); } @Override public void onViewDetachedFromWindow(View v) { - mPipOptional.ifPresent( - pip -> pip.removeMediaListener(mPipMediaListener)); + mPipController.removeMediaListener(mPipMediaListener); } }; @@ -110,12 +104,11 @@ public class PipControlsViewController { } }; - @Inject - public PipControlsViewController(PipControlsView view, Optional<Pip> pipOptional, - LayoutInflater layoutInflater, @Main Handler handler) { + public PipControlsViewController(PipControlsView view, PipController pipController, + LayoutInflater layoutInflater, Handler handler) { super(); mView = view; - mPipOptional = pipOptional; + mPipController = pipController; mLayoutInflater = layoutInflater; mHandler = handler; @@ -126,34 +119,29 @@ public class PipControlsViewController { View fullButtonView = mView.getFullButtonView(); fullButtonView.setOnFocusChangeListener(mFocusChangeListener); - fullButtonView.setOnClickListener( - v -> mPipOptional.ifPresent(pip -> pip.movePipToFullscreen()) - ); + fullButtonView.setOnClickListener(mView -> mPipController.movePipToFullscreen()); View closeButtonView = mView.getCloseButtonView(); closeButtonView.setOnFocusChangeListener(mFocusChangeListener); closeButtonView.setOnClickListener(v -> { - mPipOptional.ifPresent(pip -> pip.closePip()); + mPipController.closePip(); if (mListener != null) { mListener.onClosed(); } }); - mPlayPauseButtonView = mView.getPlayPauseButtonView(); mPlayPauseButtonView.setOnFocusChangeListener(mFocusChangeListener); mPlayPauseButtonView.setOnClickListener(v -> { if (mMediaController == null || mMediaController.getPlaybackState() == null) { return; } - mPipOptional.ifPresent(pip -> { - final int playbackState = pip.getPlaybackState(); - if (playbackState == PipController.PLAYBACK_STATE_PAUSED) { - mMediaController.getTransportControls().play(); - } else if (playbackState == PipController.PLAYBACK_STATE_PLAYING) { - mMediaController.getTransportControls().pause(); - } - }); + final int playbackState = mPipController.getPlaybackState(); + if (playbackState == PipController.PLAYBACK_STATE_PAUSED) { + mMediaController.getTransportControls().play(); + } else if (playbackState == PipController.PLAYBACK_STATE_PLAYING) { + mMediaController.getTransportControls().pause(); + } // View will be updated later in {@link mMediaControllerCallback} }); @@ -161,7 +149,7 @@ public class PipControlsViewController { private void updateMediaController() { AtomicReference<MediaController> newController = new AtomicReference<>(); - mPipOptional.ifPresent(pip -> newController.set(pip.getMediaController())); + newController.set(mPipController.getMediaController()); if (newController.get() == null || mMediaController == newController.get()) { return; @@ -224,7 +212,7 @@ public class PipControlsViewController { mPlayPauseButtonView.setVisibility(View.GONE); } else { AtomicInteger state = new AtomicInteger(PipController.STATE_UNKNOWN); - mPipOptional.ifPresent(pip -> state.set(pip.getPlaybackState())); + state.set(mPipController.getPlaybackState()); if (state.get() == PipController.STATE_UNKNOWN || state.get() == PipController.PLAYBACK_STATE_UNAVAILABLE) { mPlayPauseButtonView.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuActivity.java index 7e812d9ca8a1..06d2408c95f4 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.tv; +package com.android.wm.shell.pip.tv; import android.animation.Animator; import android.animation.AnimatorInflater; @@ -25,59 +25,40 @@ import android.content.pm.ParceledListSlice; import android.os.Bundle; import android.util.Log; -import com.android.systemui.pip.Pip; -import com.android.systemui.pip.tv.dagger.TvPipComponent; import com.android.wm.shell.R; import java.util.Collections; -import java.util.Optional; - -import javax.inject.Inject; /** * Activity to show the PIP menu to control PIP. + * TODO(b/169395392) Refactor PipMenuActivity to PipMenuView */ - public class PipMenuActivity extends Activity implements PipController.Listener { private static final String TAG = "PipMenuActivity"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); static final String EXTRA_CUSTOM_ACTIONS = "custom_actions"; - private final TvPipComponent.Builder mPipComponentBuilder; - private TvPipComponent mTvPipComponent; - private final Optional<Pip> mPipOptional; + private static PipController sPipController; private Animator mFadeInAnimation; private Animator mFadeOutAnimation; private boolean mRestorePipSizeWhenClose; private PipControlsViewController mPipControlsViewController; - @Inject - public PipMenuActivity(TvPipComponent.Builder pipComponentBuilder, - Optional<Pip> pipOptional) { - super(); - mPipComponentBuilder = pipComponentBuilder; - mPipOptional = pipOptional; - } - @Override protected void onCreate(Bundle bundle) { if (DEBUG) Log.d(TAG, "onCreate()"); super.onCreate(bundle); - mPipOptional.ifPresent(pip -> { - if (!pip.isPipShown()) { - finish(); - } - }); + if (sPipController == null || sPipController.isPipShown()) { + finish(); + } setContentView(R.layout.tv_pip_menu); - mTvPipComponent = mPipComponentBuilder.pipControlsView( - findViewById(R.id.pip_controls)).build(); - mPipControlsViewController = mTvPipComponent.getPipControlsViewController(); - - mPipOptional.ifPresent(pip -> pip.addListener(this)); - + mPipControlsViewController = new PipControlsViewController( + findViewById(R.id.pip_controls), sPipController, + getLayoutInflater(), getApplicationContext().getMainThreadHandler()); + sPipController.addListener(this); mRestorePipSizeWhenClose = true; mFadeInAnimation = AnimatorInflater.loadAnimator( this, R.anim.tv_pip_menu_fade_in_animation); @@ -104,7 +85,7 @@ public class PipMenuActivity extends Activity implements PipController.Listener if (DEBUG) Log.d(TAG, " > restoring to the default position"); // When PIP menu activity is closed, restore to the default position. - mPipOptional.ifPresent(pip -> pip.resizePinnedStack(PipController.STATE_PIP)); + sPipController.resizePinnedStack(PipController.STATE_PIP); } finish(); } @@ -131,9 +112,9 @@ public class PipMenuActivity extends Activity implements PipController.Listener if (DEBUG) Log.d(TAG, "onDestroy()"); super.onDestroy(); - mPipOptional.ifPresent(pip -> pip.removeListener(this)); - mPipOptional.ifPresent(pip -> pip.resumePipResizing( - PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH)); + sPipController.removeListener(this); + sPipController.resumePipResizing( + PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH); } @Override @@ -184,8 +165,8 @@ public class PipMenuActivity extends Activity implements PipController.Listener if (DEBUG) Log.d(TAG, "onPipResizeAboutToStart()"); finish(); - mPipOptional.ifPresent(pip -> pip.suspendPipResizing( - PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH)); + sPipController.suspendPipResizing( + PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH); } @Override @@ -194,4 +175,16 @@ public class PipMenuActivity extends Activity implements PipController.Listener super.finish(); } + + /** + * TODO(b/169395392) Refactor PipMenuActivity to PipMenuView + * + * @param pipController The singleton pipController instance for TV + */ + public static void setPipController(PipController pipController) { + if (sPipController != null) { + return; + } + sPipController = pipController; + } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java index 0666811db3da..7433085e6fcb 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.pip.tv; +package com.android.wm.shell.pip.tv; import android.app.Notification; import android.app.NotificationManager; @@ -37,7 +37,6 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.systemui.util.NotificationChannels; import com.android.wm.shell.R; /** @@ -53,6 +52,8 @@ public class PipNotification { private static final String ACTION_MENU = "PipNotification.menu"; private static final String ACTION_CLOSE = "PipNotification.close"; + public static final String NOTIFICATION_CHANNEL_TVPIP = "TPP"; + private final PackageManager mPackageManager; private final PipController mPipController; @@ -169,7 +170,7 @@ public class PipNotification { mNotificationManager = (NotificationManager) context.getSystemService( Context.NOTIFICATION_SERVICE); - mNotificationBuilder = new Notification.Builder(context, NotificationChannels.TVPIP) + mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL_TVPIP) .setLocalOnly(true) .setOngoing(false) .setCategory(Notification.CATEGORY_SYSTEM) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index e3029e55a214..a0ce9dabffe6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -28,6 +28,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { // with those in the framework ProtoLogGroup WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), + WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, + Consts.TAG_WM_SHELL), TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest"); private final boolean mEnabled; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java index edbbd69ce0dc..2b14e8bf88d6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java @@ -845,15 +845,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, } void enterSplitMode(boolean isHomeStackResizable) { - post(() -> { - final SurfaceControl sc = getWindowSurfaceControl(); - if (sc == null) { - return; - } - Transaction t = mTiles.getTransaction(); - t.show(sc).apply(); - mTiles.releaseTransaction(t); - }); + setHidden(false); SnapTarget miniMid = mSplitLayout.getMinimizedSnapAlgorithm(isHomeStackResizable).getMiddleTarget(); @@ -880,14 +872,19 @@ public class DividerView extends FrameLayout implements OnTouchListener, } void exitSplitMode() { - // Reset tile bounds + // The view is going to be removed right after this function involved, updates the surface + // in the current thread instead of posting it to the view's UI thread. final SurfaceControl sc = getWindowSurfaceControl(); if (sc == null) { return; } Transaction t = mTiles.getTransaction(); - t.hide(sc).apply(); + t.hide(sc); + mImeController.setDimsHidden(t, true); + t.apply(); mTiles.releaseTransaction(t); + + // Reset tile bounds int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; mWindowManagerProxy.applyResizeSplits(midPos, mSplitLayout); } @@ -1319,34 +1316,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, mBackground.getRight(), mBackground.getBottom(), Op.UNION); } - void onDockedTopTask() { - mState.animateAfterRecentsDrawn = true; - startDragging(false /* animate */, false /* touching */); - updateDockSide(); - mEntranceAnimationRunning = true; - - resizeStackSurfaces(calculatePositionForInsetBounds(), - mSplitLayout.getSnapAlgorithm().getMiddleTarget().position, - mSplitLayout.getSnapAlgorithm().getMiddleTarget(), - null /* transaction */); - } - - void onRecentsDrawn() { - updateDockSide(); - final int position = calculatePositionForInsetBounds(); - if (mState.animateAfterRecentsDrawn) { - mState.animateAfterRecentsDrawn = false; - - mHandler.post(() -> { - // Delay switching resizing mode because this might cause jank in recents animation - // that's longer than this animation. - stopDragging(position, getSnapAlgorithm().getMiddleTarget(), - mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN, - 200 /* endDelay */); - }); - } - } - void onUndockingTask() { int dockSide = mSplitLayout.getPrimarySplitSide(); if (inSplitMode()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 58106c94a000..985dff20ad32 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -48,14 +48,6 @@ public interface SplitScreen { /** Switch to minimized state if appropriate. */ void setMinimized(boolean minimized); - /** - * Workaround for b/62528361, at the time recents has drawn, it may happen before a - * configuration change to the Divider, and internally, the event will be posted to the - * subscriber, or DividerView, which has been removed and prevented from resizing. Instead, - * register the event handler here and proxy the event to the current DividerView. - */ - void onRecentsDrawn(); - /** Called when there's an activity forced resizable. */ void onActivityForcedResizable(String packageName, int taskId, int reason); @@ -68,9 +60,6 @@ public interface SplitScreen { /** Called when there's a task undocking. */ void onUndockingTask(); - /** Called when top task docked. */ - void onDockedTopTask(); - /** Called when app transition finished. */ void onAppTransitionFinished(); @@ -85,4 +74,13 @@ public interface SplitScreen { /** @return the container token for the secondary split root task. */ WindowContainerToken getSecondaryRoot(); + + /** + * Splits the primary task if feasible, this is to preserve legacy way to toggle split screen. + * Like triggering split screen through long pressing recents app button or through + * {@link android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN}. + * + * @return {@code true} if it successes to split the primary task. + */ + boolean splitPrimaryTask(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index c8be93747f19..43e4d62baaf6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -16,19 +16,25 @@ package com.android.wm.shell.splitscreen; +import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.Display.DEFAULT_DISPLAY; +import android.app.ActivityManager.RunningTaskInfo; 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; import android.view.LayoutInflater; import android.view.View; +import android.widget.Toast; import android.window.TaskOrganizer; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -40,12 +46,14 @@ 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.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TransactionPool; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -56,7 +64,7 @@ public class SplitScreenController implements SplitScreen, DisplayController.OnDisplaysChangedListener { static final boolean DEBUG = false; - private static final String TAG = "Divider"; + private static final String TAG = "SplitScreenCtrl"; private static final int DEFAULT_APP_TRANSITION_DURATION = 336; private final Context mContext; @@ -99,7 +107,7 @@ public class SplitScreenController implements SplitScreen, public SplitScreenController(Context context, DisplayController displayController, SystemWindows systemWindows, DisplayImeController imeController, Handler handler, TransactionPool transactionPool, - ShellTaskOrganizer shellTaskOrganizer) { + ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue) { mContext = context; mDisplayController = displayController; mSystemWindows = systemWindows; @@ -107,8 +115,7 @@ public class SplitScreenController implements SplitScreen, mHandler = handler; mForcedResizableController = new ForcedResizableInfoActivityController(context, this); mTransactionPool = transactionPool; - mWindowManagerProxy = new WindowManagerProxy(mTransactionPool, mHandler, - shellTaskOrganizer); + mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer); mTaskOrganizer = shellTaskOrganizer; mSplits = new SplitScreenTaskOrganizer(this, shellTaskOrganizer); mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler, @@ -157,12 +164,12 @@ public class SplitScreenController implements SplitScreen, // Don't initialize the divider or anything until we get the default display. } - /** Returns {@code true} if split screen is supported on the device. */ + @Override public boolean isSplitScreenSupported() { return mSplits.isSplitScreenSupported(); } - /** Called when keyguard showing state changed. */ + @Override public void onKeyguardVisibilityChanged(boolean showing) { if (!isSplitActive() || mView == null) { return; @@ -229,21 +236,22 @@ public class SplitScreenController implements SplitScreen, mHandler.post(task); } - /** Returns {@link DividerView}. */ + @Override public DividerView getDividerView() { return mView; } - /** Returns {@code true} if one of the split screen is in minimized mode. */ + @Override public boolean isMinimized() { return mMinimized; } + @Override public boolean isHomeStackResizable() { return mHomeStackResizable; } - /** Returns {@code true} if the divider is visible. */ + @Override public boolean isDividerVisible() { return mView != null && mView.getVisibility() == View.VISIBLE; } @@ -328,7 +336,7 @@ public class SplitScreenController implements SplitScreen, } } - /** Switch to minimized state if appropriate. */ + @Override public void setMinimized(final boolean minimized) { if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible); mHandler.post(() -> { @@ -392,48 +400,29 @@ public class SplitScreenController implements SplitScreen, mWindowManager.setTouchable(!mAdjustedForIme); } - /** - * Workaround for b/62528361, at the time recents has drawn, it may happen before a - * configuration change to the Divider, and internally, the event will be posted to the - * subscriber, or DividerView, which has been removed and prevented from resizing. Instead, - * register the event handler here and proxy the event to the current DividerView. - */ - public void onRecentsDrawn() { - if (mView != null) { - mView.onRecentsDrawn(); - } - } - - /** Called when there's an activity forced resizable. */ + @Override public void onActivityForcedResizable(String packageName, int taskId, int reason) { mForcedResizableController.activityForcedResizable(packageName, taskId, reason); } - /** Called when there's an activity dismissing split screen. */ + @Override public void onActivityDismissingSplitScreen() { mForcedResizableController.activityDismissingSplitScreen(); } - /** Called when there's an activity launch on secondary display failed. */ + @Override public void onActivityLaunchOnSecondaryDisplayFailed() { mForcedResizableController.activityLaunchOnSecondaryDisplayFailed(); } - /** Called when there's a task undocking. */ + @Override public void onUndockingTask() { if (mView != null) { mView.onUndockingTask(); } } - /** Called when top task docked. */ - public void onDockedTopTask() { - if (mView != null) { - mView.onDockedTopTask(); - } - } - - /** Called when app transition finished. */ + @Override public void onAppTransitionFinished() { if (mView == null) { return; @@ -441,7 +430,7 @@ public class SplitScreenController implements SplitScreen, mForcedResizableController.onAppTransitionFinished(); } - /** Dumps current status of Split Screen. */ + @Override public void dump(PrintWriter pw) { pw.print(" mVisible="); pw.println(mVisible); pw.print(" mMinimized="); pw.println(mMinimized); @@ -458,7 +447,7 @@ public class SplitScreenController implements SplitScreen, return (long) (transitionDuration * transitionScale); } - /** Registers listener that gets called whenever the existence of the divider changes. */ + @Override public void registerInSplitScreenListener(Consumer<Boolean> listener) { listener.accept(isDividerVisible()); synchronized (mDockedStackExistsListeners) { @@ -473,6 +462,42 @@ public class SplitScreenController implements SplitScreen, } } + @Override + public boolean splitPrimaryTask() { + try { + if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED + || isSplitActive()) { + return false; + } + + // Try fetching the top running task. + final List<RunningTaskInfo> runningTasks = + ActivityTaskManager.getService().getTasks(1 /* maxNum */); + if (runningTasks == null || runningTasks.isEmpty()) { + return false; + } + // Note: The set of running tasks from the system is ordered by recency. + final RunningTaskInfo topRunningTask = runningTasks.get(0); + + final int activityType = topRunningTask.configuration.windowConfiguration + .getActivityType(); + if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { + return false; + } + + if (!topRunningTask.supportsSplitScreenMultiWindow) { + Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text, + Toast.LENGTH_SHORT).show(); + return false; + } + + return ActivityTaskManager.getService().setTaskWindowingModeSplitScreenPrimary( + topRunningTask.taskId, true /* onTop */); + } catch (RemoteException e) { + return false; + } + } + /** Notifies the bounds of split screen changed. */ void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) { synchronized (mBoundsChangedListeners) { @@ -532,7 +557,7 @@ public class SplitScreenController implements SplitScreen, return mWindowManagerProxy; } - /** @return the container token for the secondary split root task. */ + @Override public WindowContainerToken getSecondaryRoot() { if (mSplits == null || mSplits.mSecondary == null) { return null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java index 25827cdb9e24..47e7c99d2268 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java @@ -28,7 +28,6 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.graphics.Rect; -import android.os.Handler; import android.os.RemoteException; import android.util.Log; import android.view.Display; @@ -41,7 +40,6 @@ import android.window.WindowOrganizer; import com.android.internal.annotations.GuardedBy; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.common.TransactionPool; import java.util.ArrayList; import java.util.List; @@ -85,9 +83,8 @@ class WindowManagerProxy { private final TaskOrganizer mTaskOrganizer; - WindowManagerProxy(TransactionPool transactionPool, Handler handler, - TaskOrganizer taskOrganizer) { - mSyncTransactionQueue = new SyncTransactionQueue(transactionPool, handler); + WindowManagerProxy(SyncTransactionQueue syncQueue, TaskOrganizer taskOrganizer) { + mSyncTransactionQueue = syncQueue; mTaskOrganizer = taskOrganizer; } diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index 937b00b3a0fd..9940ea575873 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -23,20 +23,30 @@ android_test { "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", + "androidx.dynamicanimation_dynamicanimation", + "dagger2", + "kotlinx-coroutines-android", + "kotlinx-coroutines-core", "mockito-target-extended-minus-junit4", "truth-prebuilt", "testables", ], + libs: [ "android.test.mock", "android.test.base", "android.test.runner", ], + jni_libs: [ "libdexmakerjvmtiagent", "libstaticjvmtiagent", ], + kotlincflags: ["-Xjvm-default=enable"], + + plugins: ["dagger2-compiler"], + optimize: { enabled: false, }, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index 7b499d4d6e7d..1bc5cea40a8b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -33,6 +33,8 @@ import android.window.ITaskOrganizerController; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import org.junit.Before; @@ -54,7 +56,9 @@ public class ShellTaskOrganizerTests { private ITaskOrganizerController mTaskOrganizerController; ShellTaskOrganizer mOrganizer; + private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class); private final TransactionPool mTransactionPool = mock(TransactionPool.class); + private final ShellExecutor mTestExecutor = mock(ShellExecutor.class); private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener { final ArrayList<RunningTaskInfo> appeared = new ArrayList<>(); @@ -85,7 +89,8 @@ public class ShellTaskOrganizerTests { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mTransactionPool); + mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue, + mTransactionPool, mTestExecutor, mTestExecutor); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt index bd596800e86d..4bd9bed26a82 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt @@ -1,4 +1,20 @@ -package com.android.systemui.util.animation +/* + * 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.animation import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -11,11 +27,11 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringForce import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.util.animation.PhysicsAnimator.EndListener -import com.android.systemui.util.animation.PhysicsAnimator.UpdateListener -import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames -import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.getAnimationUpdateFrames -import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.verifyAnimationUpdateFrames +import com.android.wm.shell.animation.PhysicsAnimator.EndListener +import com.android.wm.shell.animation.PhysicsAnimator.UpdateListener +import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames +import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.getAnimationUpdateFrames +import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.verifyAnimationUpdateFrames import org.junit.After import org.junit.Assert import org.junit.Assert.assertEquals diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java new file mode 100644 index 000000000000..080cddc58a09 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.InsetsState.ITYPE_IME; +import static android.view.Surface.ROTATION_0; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.graphics.Point; +import android.view.InsetsSourceControl; +import android.view.InsetsState; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; + +import com.android.internal.view.IInputMethodManager; + +import org.junit.Before; +import org.junit.Test; + +@SmallTest +public class DisplayImeControllerTest { + + private SurfaceControl.Transaction mT; + private DisplayImeController.PerDisplay mPerDisplay; + private IInputMethodManager mMock; + + @Before + public void setUp() throws Exception { + mT = mock(SurfaceControl.Transaction.class); + mMock = mock(IInputMethodManager.class); + mPerDisplay = new DisplayImeController(null, null, Runnable::run, new TransactionPool() { + @Override + public SurfaceControl.Transaction acquire() { + return mT; + } + + @Override + public void release(SurfaceControl.Transaction t) { + } + }) { + @Override + public IInputMethodManager getImms() { + return mMock; + } + }.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0); + } + + @Test + public void reappliesVisibilityToChangedLeash() { + verifyZeroInteractions(mT); + + mPerDisplay.mImeShowing = false; + mPerDisplay.insetsControlChanged(new InsetsState(), new InsetsSourceControl[] { + new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0)) + }); + + verify(mT).hide(any()); + + mPerDisplay.mImeShowing = true; + mPerDisplay.insetsControlChanged(new InsetsState(), new InsetsSourceControl[] { + new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0)) + }); + + verify(mT).show(any()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt index 251ca9c8dcb2..fe536411d5ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.util.magnetictarget +package com.android.wm.shell.common.magnetictarget import android.testing.AndroidTestingRunner import android.testing.TestableLooper diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java index 89ca32c1f1a5..7d51886e8ec0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java @@ -16,8 +16,8 @@ package com.android.systemui.pip; -import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; -import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -32,7 +32,9 @@ import android.view.SurfaceControl; import androidx.test.filters.SmallTest; -import com.android.systemui.SysuiTestCase; +import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.pip.PipSurfaceTransactionHelper; +import com.android.wm.shell.pip.PipTestCase; import org.junit.Before; import org.junit.Test; @@ -47,7 +49,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -public class PipAnimationControllerTest extends SysuiTestCase { +public class PipAnimationControllerTest extends PipTestCase { private PipAnimationController mPipAnimationController; @@ -161,7 +163,7 @@ public class PipAnimationControllerTest extends SysuiTestCase { * A dummy {@link SurfaceControl.Transaction} class. * This is created as {@link Mock} does not support method chaining. */ - private static class DummySurfaceControlTx extends SurfaceControl.Transaction { + public static class DummySurfaceControlTx extends SurfaceControl.Transaction { @Override public SurfaceControl.Transaction setAlpha(SurfaceControl leash, float alpha) { return this; diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java index cdb177096f11..f514b0be8dc7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java @@ -31,7 +31,8 @@ import android.view.Gravity; import androidx.test.filters.SmallTest; -import com.android.systemui.SysuiTestCase; +import com.android.wm.shell.pip.PipBoundsHandler; +import com.android.wm.shell.pip.PipTestCase; import org.junit.Before; import org.junit.Test; @@ -46,7 +47,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidTestingRunner.class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -public class PipBoundsHandlerTest extends SysuiTestCase { +public class PipBoundsHandlerTest extends PipTestCase { private static final int ROUNDING_ERROR_MARGIN = 16; private static final float ASPECT_RATIO_ERROR_MARGIN = 0.01f; private static final float DEFAULT_ASPECT_RATIO = 1f; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTestCase.java new file mode 100644 index 000000000000..fdebe4e4e6f5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTestCase.java @@ -0,0 +1,53 @@ +/* + * 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; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.testing.TestableContext; + +import androidx.test.InstrumentationRegistry; + +import org.junit.Before; + +/** + * Base class that does One Handed specific setup. + */ +public abstract class PipTestCase { + + protected TestableContext mContext; + + @Before + public void setup() { + final Context context = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + final DisplayManager dm = context.getSystemService(DisplayManager.class); + mContext = new TestableContext( + context.createDisplayContext(dm.getDisplay(DEFAULT_DISPLAY))); + + InstrumentationRegistry + .getInstrumentation() + .getUiAutomation() + .adoptShellPermissionIdentity(); + } + + protected Context getContext() { + return mContext; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 1274621623ea..d305c6491ba5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -31,13 +31,15 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.pip.PipBoundsHandler; -import com.android.systemui.pip.PipTaskOrganizer; -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.wmshell.WindowManagerShellWrapper; +import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.pip.PipBoundsHandler; +import com.android.wm.shell.pip.PipTaskOrganizer; +import com.android.wm.shell.pip.PipTestCase; +import com.android.wm.shell.pip.phone.PipAppOpsListener; +import com.android.wm.shell.pip.phone.PipController; +import com.android.wm.shell.pip.phone.PipMediaController; +import com.android.wm.shell.pip.phone.PipTouchHandler; import org.junit.Before; import org.junit.Test; @@ -51,15 +53,14 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -public class PipControllerTest extends SysuiTestCase { - private PipController mPipController; +public class PipControllerTest extends PipTestCase { + private com.android.wm.shell.pip.phone.PipController mPipController; private TestableContext mSpiedContext; - @Mock private ActivityManagerWrapper mMockActivityManagerWrapper; - @Mock private ConfigurationController mMockConfigurationController; @Mock private DisplayController mMockdDisplayController; @Mock private PackageManager mPackageManager; - @Mock private PipMenuActivityController mMockPipMenuActivityController; + @Mock private com.android.wm.shell.pip.phone.PipMenuActivityController + mMockPipMenuActivityController; @Mock private PipAppOpsListener mMockPipAppOpsListener; @Mock private PipBoundsHandler mMockPipBoundsHandler; @Mock private PipMediaController mMockPipMediaController; @@ -77,14 +78,9 @@ public class PipControllerTest extends SysuiTestCase { when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager); mPipController = new PipController(mSpiedContext, mMockdDisplayController, - mMockPipAppOpsListener, - mMockPipBoundsHandler, mMockPipMediaController, mMockPipMenuActivityController, - mMockPipTaskOrganizer, mMockPipTouchHandler, mMockWindowManagerShellWrapper); - } - - @Test - public void testNonPipDevice_shouldNotRegisterTaskStackListener() { - verify(mMockActivityManagerWrapper, never()).registerTaskStackListener(any()); + mMockPipAppOpsListener, mMockPipBoundsHandler, mMockPipMediaController, + mMockPipMenuActivityController, mMockPipTaskOrganizer, mMockPipTouchHandler, + mMockWindowManagerShellWrapper); } @Test @@ -101,9 +97,4 @@ public class PipControllerTest extends SysuiTestCase { public void testNonPipDevice_shouldNotAddDisplayWindowListener() { verify(mMockdDisplayController, never()).addDisplayWindowListener(any()); } - - @Test - public void testNonPipDevice_shouldNotAddCallback() { - verify(mMockConfigurationController, never()).addCallback(any()); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java index b1a7df83fa9f..663169fbeccc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java @@ -32,13 +32,13 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.pip.PipBoundsHandler; -import com.android.systemui.pip.PipSurfaceTransactionHelper; -import com.android.systemui.pip.PipTaskOrganizer; -import com.android.systemui.pip.PipUiEventLogger; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.pip.PipBoundsHandler; +import com.android.wm.shell.pip.PipSurfaceTransactionHelper; +import com.android.wm.shell.pip.PipTaskOrganizer; +import com.android.wm.shell.pip.PipTestCase; +import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.splitscreen.SplitScreen; import org.junit.Before; @@ -55,7 +55,7 @@ import java.util.Optional; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -public class PipTaskOrganizerTest extends SysuiTestCase { +public class PipTaskOrganizerTest extends PipTestCase { private PipTaskOrganizer mSpiedPipTaskOrganizer; private TestableContext mSpiedContext; diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index ad83ebbc76fd..c96cb20e4df4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -30,13 +30,17 @@ import android.util.Size; import androidx.test.filters.SmallTest; -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.pip.PipBoundsHandler; -import com.android.systemui.pip.PipSnapAlgorithm; -import com.android.systemui.pip.PipTaskOrganizer; -import com.android.systemui.pip.PipUiEventLogger; -import com.android.systemui.util.FloatingContentCoordinator; +import com.android.wm.shell.R; +import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.pip.PipBoundsHandler; +import com.android.wm.shell.pip.PipSnapAlgorithm; +import com.android.wm.shell.pip.PipTaskOrganizer; +import com.android.wm.shell.pip.PipTestCase; +import com.android.wm.shell.pip.PipUiEventLogger; +import com.android.wm.shell.pip.phone.PipMenuActivityController; +import com.android.wm.shell.pip.phone.PipMotionHelper; +import com.android.wm.shell.pip.phone.PipResizeGestureHandler; +import com.android.wm.shell.pip.phone.PipTouchHandler; import org.junit.Before; import org.junit.Test; @@ -54,7 +58,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -public class PipTouchHandlerTest extends SysuiTestCase { +public class PipTouchHandlerTest extends PipTestCase { private PipTouchHandler mPipTouchHandler; diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java index 17b2e3225200..270213049771 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ package com.android.systemui.pip.phone; @@ -35,7 +35,8 @@ import android.view.ViewConfiguration; import androidx.test.filters.SmallTest; -import com.android.systemui.SysuiTestCase; +import com.android.wm.shell.pip.PipTestCase; +import com.android.wm.shell.pip.phone.PipTouchState; import org.junit.Before; import org.junit.Test; @@ -46,7 +47,7 @@ import java.util.concurrent.CountDownLatch; @RunWith(AndroidTestingRunner.class) @SmallTest @RunWithLooper -public class PipTouchStateTest extends SysuiTestCase { +public class PipTouchStateTest extends PipTestCase { private PipTouchState mTouchState; private CountDownLatch mDoubleTapCallbackTriggeredLatch; @@ -157,4 +158,4 @@ public class PipTouchStateTest extends SysuiTestCase { private MotionEvent createMotionEvent(int action, long eventTime, float x, float y) { return MotionEvent.obtain(0, eventTime, action, x, y, 0); } -}
\ No newline at end of file +} diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp index 6f05cbd0ebb3..71c8e1f6121f 100644 --- a/libs/androidfw/CursorWindow.cpp +++ b/libs/androidfw/CursorWindow.cpp @@ -30,23 +30,62 @@ namespace android { -CursorWindow::CursorWindow(const String8& name, int ashmemFd, - void* data, size_t size, bool readOnly) : - mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size), mReadOnly(readOnly) { +/** + * By default windows are lightweight inline allocations of this size; + * they're only inflated to ashmem regions when more space is needed. + */ +static constexpr const size_t kInlineSize = 16384; + +CursorWindow::CursorWindow(const String8& name, int ashmemFd, void* data, size_t size, + size_t inflatedSize, bool readOnly) : + mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size), + mInflatedSize(inflatedSize), mReadOnly(readOnly) { mHeader = static_cast<Header*>(mData); } CursorWindow::~CursorWindow() { - ::munmap(mData, mSize); - ::close(mAshmemFd); + if (mAshmemFd != -1) { + ::munmap(mData, mSize); + ::close(mAshmemFd); + } else { + free(mData); + } +} + +status_t CursorWindow::create(const String8& name, size_t inflatedSize, + CursorWindow** outCursorWindow) { + *outCursorWindow = nullptr; + + size_t size = std::min(kInlineSize, inflatedSize); + void* data = calloc(size, 1); + if (!data) return NO_MEMORY; + + CursorWindow* window = new CursorWindow(name, -1, data, size, + inflatedSize, false /*readOnly*/); + status_t result = window->clear(); + if (!result) { + LOG_WINDOW("Created new CursorWindow: freeOffset=%d, " + "numRows=%d, numColumns=%d, mSize=%zu, mData=%p", + window->mHeader->freeOffset, + window->mHeader->numRows, + window->mHeader->numColumns, + window->mSize, window->mData); + *outCursorWindow = window; + return OK; + } + delete window; + return result; } -status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** outCursorWindow) { +status_t CursorWindow::inflate() { + // Shortcut when we can't expand any further + if (mSize == mInflatedSize) return INVALID_OPERATION; + String8 ashmemName("CursorWindow: "); - ashmemName.append(name); + ashmemName.append(mName); status_t result; - int ashmemFd = ashmem_create_region(ashmemName.string(), size); + int ashmemFd = ashmem_create_region(ashmemName.string(), mInflatedSize); if (ashmemFd < 0) { result = -errno; ALOGE("CursorWindow: ashmem_create_region() failed: errno=%d.", errno); @@ -55,7 +94,8 @@ status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** o if (result < 0) { ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d",errno); } else { - void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0); + void* data = ::mmap(NULL, mInflatedSize, PROT_READ | PROT_WRITE, + MAP_SHARED, ashmemFd, 0); if (data == MAP_FAILED) { result = -errno; ALOGE("CursorWindow: mmap() failed: errno=%d.", errno); @@ -64,33 +104,49 @@ status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** o if (result < 0) { ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d.", errno); } else { - CursorWindow* window = new CursorWindow(name, ashmemFd, - data, size, false /*readOnly*/); - result = window->clear(); - if (!result) { - LOG_WINDOW("Created new CursorWindow: freeOffset=%d, " - "numRows=%d, numColumns=%d, mSize=%zu, mData=%p", - window->mHeader->freeOffset, - window->mHeader->numRows, - window->mHeader->numColumns, - window->mSize, window->mData); - *outCursorWindow = window; - return OK; - } - delete window; + // Move inline contents into new ashmem region + memcpy(data, mData, mSize); + free(mData); + mAshmemFd = ashmemFd; + mData = data; + mHeader = static_cast<Header*>(mData); + mSize = mInflatedSize; + LOG_WINDOW("Inflated CursorWindow: freeOffset=%d, " + "numRows=%d, numColumns=%d, mSize=%zu, mData=%p", + mHeader->freeOffset, + mHeader->numRows, + mHeader->numColumns, + mSize, mData); + return OK; } } - ::munmap(data, size); + ::munmap(data, mInflatedSize); } ::close(ashmemFd); } - *outCursorWindow = NULL; return result; } status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow) { - String8 name = parcel->readString8(); + *outCursorWindow = nullptr; + + String8 name; + status_t result = parcel->readString8(&name); + if (result) return result; + + bool isAshmem; + result = parcel->readBool(&isAshmem); + if (result) return result; + if (isAshmem) { + return createFromParcelAshmem(parcel, name, outCursorWindow); + } else { + return createFromParcelInline(parcel, name, outCursorWindow); + } +} + +status_t CursorWindow::createFromParcelAshmem(Parcel* parcel, String8& name, + CursorWindow** outCursorWindow) { status_t result; int actualSize; int ashmemFd = parcel->readFileDescriptor(); @@ -122,8 +178,8 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursor actualSize, (int) size, errno); } else { CursorWindow* window = new CursorWindow(name, dupAshmemFd, - data, size, true /*readOnly*/); - LOG_WINDOW("Created CursorWindow from parcel: freeOffset=%d, " + data, size, size, true /*readOnly*/); + LOG_WINDOW("Created CursorWindow from ashmem parcel: freeOffset=%d, " "numRows=%d, numColumns=%d, mSize=%zu, mData=%p", window->mHeader->freeOffset, window->mHeader->numRows, @@ -140,12 +196,62 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursor return result; } +status_t CursorWindow::createFromParcelInline(Parcel* parcel, String8& name, + CursorWindow** outCursorWindow) { + uint32_t sentSize; + status_t result = parcel->readUint32(&sentSize); + if (result) return result; + if (sentSize > kInlineSize) return NO_MEMORY; + + void* data = calloc(sentSize, 1); + if (!data) return NO_MEMORY; + + result = parcel->read(data, sentSize); + if (result) return result; + + CursorWindow* window = new CursorWindow(name, -1, data, sentSize, + sentSize, true /*readOnly*/); + LOG_WINDOW("Created CursorWindow from inline parcel: freeOffset=%d, " + "numRows=%d, numColumns=%d, mSize=%zu, mData=%p", + window->mHeader->freeOffset, + window->mHeader->numRows, + window->mHeader->numColumns, + window->mSize, window->mData); + *outCursorWindow = window; + return OK; +} + status_t CursorWindow::writeToParcel(Parcel* parcel) { - status_t status = parcel->writeString8(mName); - if (!status) { - status = parcel->writeDupFileDescriptor(mAshmemFd); + LOG_WINDOW("Writing CursorWindow: freeOffset=%d, " + "numRows=%d, numColumns=%d, mSize=%zu, mData=%p", + mHeader->freeOffset, + mHeader->numRows, + mHeader->numColumns, + mSize, mData); + + status_t result = parcel->writeString8(mName); + if (result) return result; + + if (mAshmemFd != -1) { + result = parcel->writeBool(true); + if (result) return result; + return writeToParcelAshmem(parcel); + } else { + result = parcel->writeBool(false); + if (result) return result; + return writeToParcelInline(parcel); } - return status; +} + +status_t CursorWindow::writeToParcelAshmem(Parcel* parcel) { + return parcel->writeDupFileDescriptor(mAshmemFd); +} + +status_t CursorWindow::writeToParcelInline(Parcel* parcel) { + status_t result = parcel->writeUint32(mHeader->freeOffset); + if (result) return result; + + return parcel->write(mData, mHeader->freeOffset); } status_t CursorWindow::clear() { @@ -187,6 +293,7 @@ status_t CursorWindow::allocRow() { if (rowSlot == NULL) { return NO_MEMORY; } + uint32_t rowSlotOffset = offsetFromPtr(rowSlot); // Allocate the slots for the field directory size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot); @@ -201,7 +308,8 @@ status_t CursorWindow::allocRow() { memset(fieldDir, 0, fieldDirSize); LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %zu bytes at offset %u\n", - mHeader->numRows - 1, offsetFromPtr(rowSlot), fieldDirSize, fieldDirOffset); + mHeader->numRows - 1, rowSlotOffset, fieldDirSize, fieldDirOffset); + rowSlot = static_cast<RowSlot*>(offsetToPtr(rowSlotOffset)); rowSlot->offset = fieldDirOffset; return OK; } @@ -229,10 +337,14 @@ uint32_t CursorWindow::alloc(size_t size, bool aligned) { uint32_t offset = mHeader->freeOffset + padding; uint32_t nextFreeOffset = offset + size; if (nextFreeOffset > mSize) { - ALOGW("Window is full: requested allocation %zu bytes, " - "free space %zu bytes, window size %zu bytes", - size, freeSpace(), mSize); - return 0; + // Try inflating to ashmem before finally giving up + inflate(); + if (nextFreeOffset > mSize) { + ALOGW("Window is full: requested allocation %zu bytes, " + "free space %zu bytes, window size %zu bytes", + size, freeSpace(), mSize); + return 0; + } } mHeader->freeOffset = nextFreeOffset; @@ -260,7 +372,10 @@ CursorWindow::RowSlot* CursorWindow::allocRowSlot() { } if (chunkPos == ROW_SLOT_CHUNK_NUM_ROWS) { if (!chunk->nextChunkOffset) { - chunk->nextChunkOffset = alloc(sizeof(RowSlotChunk), true /*aligned*/); + uint32_t chunkOffset = offsetFromPtr(chunk); + uint32_t newChunk = alloc(sizeof(RowSlotChunk), true /*aligned*/); + chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunkOffset)); + chunk->nextChunkOffset = newChunk; if (!chunk->nextChunkOffset) { return NULL; } @@ -308,6 +423,7 @@ status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column, if (!fieldSlot) { return BAD_VALUE; } + uint32_t fieldSlotOffset = offsetFromPtr(fieldSlot); uint32_t offset = alloc(size); if (!offset) { @@ -316,6 +432,7 @@ status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column, memcpy(offsetToPtr(offset), value, size); + fieldSlot = static_cast<FieldSlot*>(offsetToPtr(fieldSlotOffset)); fieldSlot->type = type; fieldSlot->data.buffer.offset = offset; fieldSlot->data.buffer.size = size; diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h index ad64b246b3f5..0bee60929cc9 100644 --- a/libs/androidfw/include/androidfw/CursorWindow.h +++ b/libs/androidfw/include/androidfw/CursorWindow.h @@ -50,8 +50,8 @@ namespace android { * Strings are stored in UTF-8. */ class CursorWindow { - CursorWindow(const String8& name, int ashmemFd, - void* data, size_t size, bool readOnly); + CursorWindow(const String8& name, int ashmemFd, void* data, size_t size, + size_t inflatedSize, bool readOnly); public: /* Field types. */ @@ -165,11 +165,12 @@ private: int mAshmemFd; void* mData; size_t mSize; + size_t mInflatedSize; bool mReadOnly; Header* mHeader; inline void* offsetToPtr(uint32_t offset, uint32_t bufferSize = 0) { - if (offset >= mSize) { + if (offset > mSize) { ALOGE("Offset %" PRIu32 " out of bounds, max value %zu", offset, mSize); return NULL; } @@ -185,6 +186,18 @@ private: return static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(mData); } + static status_t createFromParcelAshmem(Parcel*, String8&, CursorWindow**); + static status_t createFromParcelInline(Parcel*, String8&, CursorWindow**); + + status_t writeToParcelAshmem(Parcel*); + status_t writeToParcelInline(Parcel*); + + /** + * By default windows are lightweight inline allocations; this method + * inflates the window into a larger ashmem region. + */ + status_t inflate(); + /** * Allocate a portion of the window. Returns the offset * of the allocation, or 0 if there isn't enough space. diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 90d2537d97a8..155bb6ba8f75 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -327,6 +327,7 @@ cc_defaults { "jni/PathMeasure.cpp", "jni/Picture.cpp", "jni/Shader.cpp", + "jni/RenderEffect.cpp", "jni/Typeface.cpp", "jni/Utils.cpp", "jni/YuvToJpegEncoder.cpp", @@ -334,6 +335,7 @@ cc_defaults { "jni/fonts/FontFamily.cpp", "jni/text/LineBreaker.cpp", "jni/text/MeasuredText.cpp", + "jni/text/TextShaper.cpp", ], header_libs: [ "android_graphics_jni_headers" ], @@ -462,14 +464,6 @@ cc_defaults { "RenderNode.cpp", "RenderProperties.cpp", "RootRenderNode.cpp", - "shader/Shader.cpp", - "shader/BitmapShader.cpp", - "shader/BlurShader.cpp", - "shader/ComposeShader.cpp", - "shader/LinearGradientShader.cpp", - "shader/RadialGradientShader.cpp", - "shader/RuntimeShader.cpp", - "shader/SweepGradientShader.cpp", "SkiaCanvas.cpp", "VectorDrawable.cpp", ], diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp index ff9cf45cdc73..8fba9cf21df1 100644 --- a/libs/hwui/RenderProperties.cpp +++ b/libs/hwui/RenderProperties.cpp @@ -49,6 +49,12 @@ bool LayerProperties::setColorFilter(SkColorFilter* filter) { return true; } +bool LayerProperties::setImageFilter(SkImageFilter* imageFilter) { + if(mImageFilter.get() == imageFilter) return false; + mImageFilter = sk_ref_sp(imageFilter); + return true; +} + bool LayerProperties::setFromPaint(const SkPaint* paint) { bool changed = false; changed |= setAlpha(static_cast<uint8_t>(PaintUtils::getAlphaDirect(paint))); @@ -63,6 +69,7 @@ LayerProperties& LayerProperties::operator=(const LayerProperties& other) { setAlpha(other.alpha()); setXferMode(other.xferMode()); setColorFilter(other.getColorFilter()); + setImageFilter(other.getImageFilter()); return *this; } diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index ef4cd1f1eb62..aeb60e6ce355 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -27,6 +27,7 @@ #include "utils/PaintUtils.h" #include <SkBlendMode.h> +#include <SkImageFilter.h> #include <SkCamera.h> #include <SkColor.h> #include <SkMatrix.h> @@ -93,6 +94,10 @@ public: SkColorFilter* getColorFilter() const { return mColorFilter.get(); } + bool setImageFilter(SkImageFilter* imageFilter); + + SkImageFilter* getImageFilter() const { return mImageFilter.get(); } + // Sets alpha, xfermode, and colorfilter from an SkPaint // paint may be NULL, in which case defaults will be set bool setFromPaint(const SkPaint* paint); @@ -118,6 +123,7 @@ private: uint8_t mAlpha; SkBlendMode mMode; sk_sp<SkColorFilter> mColorFilter; + sk_sp<SkImageFilter> mImageFilter; }; /* @@ -541,6 +547,7 @@ public: bool promotedToLayer() const { return mLayerProperties.mType == LayerType::None && fitsOnLayer() && (mComputedFields.mNeedLayerForFunctors || + mLayerProperties.mImageFilter != nullptr || (!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 && mPrimitiveFields.mHasOverlappingRendering)); } diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index a690840e91a9..1dbce58fb7c9 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -42,8 +42,6 @@ #include <SkTextBlob.h> #include <SkVertices.h> -#include <shader/BitmapShader.h> - #include <memory> #include <optional> #include <utility> @@ -51,7 +49,6 @@ namespace android { using uirenderer::PaintUtils; -using uirenderer::BitmapShader; Canvas* Canvas::create_canvas(const SkBitmap& bitmap) { return new SkiaCanvas(bitmap); @@ -684,9 +681,7 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight, if (paint) { pnt = *paint; } - - pnt.setShader(sk_ref_sp(new BitmapShader( - bitmap.makeImage(), SkTileMode::kClamp, SkTileMode::kClamp, nullptr))); + pnt.setShader(bitmap.makeImage()->makeShader()); auto v = builder.detach(); apply_looper(&pnt, [&](const SkPaint& p) { mCanvas->drawVertices(v, SkBlendMode::kModulate, p); diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index 0f566e4b494a..cd908354aea5 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -23,6 +23,7 @@ #include "PathParser.h" #include "SkColorFilter.h" #include "SkImageInfo.h" +#include "SkShader.h" #include "hwui/Paint.h" #ifdef __ANDROID__ @@ -158,10 +159,10 @@ void FullPath::draw(SkCanvas* outCanvas, bool useStagingData) { // Draw path's fill, if fill color or gradient is valid bool needsFill = false; - Paint paint; + SkPaint paint; if (properties.getFillGradient() != nullptr) { paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha())); - paint.setShader(sk_sp<Shader>(SkSafeRef(properties.getFillGradient()))); + paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getFillGradient()))); needsFill = true; } else if (properties.getFillColor() != SK_ColorTRANSPARENT) { paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha())); @@ -178,7 +179,7 @@ void FullPath::draw(SkCanvas* outCanvas, bool useStagingData) { bool needsStroke = false; if (properties.getStrokeGradient() != nullptr) { paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha())); - paint.setShader(sk_sp<Shader>(SkSafeRef(properties.getStrokeGradient()))); + paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getStrokeGradient()))); needsStroke = true; } else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) { paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha())); diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h index d4086f1aa622..ac7d41e0d600 100644 --- a/libs/hwui/VectorDrawable.h +++ b/libs/hwui/VectorDrawable.h @@ -31,8 +31,8 @@ #include <SkPath.h> #include <SkPathMeasure.h> #include <SkRect.h> +#include <SkShader.h> #include <SkSurface.h> -#include <shader/Shader.h> #include <cutils/compiler.h> #include <stddef.h> @@ -227,20 +227,20 @@ public: strokeGradient = prop.strokeGradient; onPropertyChanged(); } - void setFillGradient(Shader* gradient) { + void setFillGradient(SkShader* gradient) { if (fillGradient.get() != gradient) { fillGradient = sk_ref_sp(gradient); onPropertyChanged(); } } - void setStrokeGradient(Shader* gradient) { + void setStrokeGradient(SkShader* gradient) { if (strokeGradient.get() != gradient) { strokeGradient = sk_ref_sp(gradient); onPropertyChanged(); } } - Shader* getFillGradient() const { return fillGradient.get(); } - Shader* getStrokeGradient() const { return strokeGradient.get(); } + SkShader* getFillGradient() const { return fillGradient.get(); } + SkShader* getStrokeGradient() const { return strokeGradient.get(); } float getStrokeWidth() const { return mPrimitiveFields.strokeWidth; } void setStrokeWidth(float strokeWidth) { VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeWidth, strokeWidth); @@ -320,8 +320,8 @@ public: count, }; PrimitiveFields mPrimitiveFields; - sk_sp<Shader> fillGradient; - sk_sp<Shader> strokeGradient; + sk_sp<SkShader> fillGradient; + sk_sp<SkShader> strokeGradient; }; // Called from UI thread diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp index 4bbf1214bdcf..dca10e29cbb8 100644 --- a/libs/hwui/apex/LayoutlibLoader.cpp +++ b/libs/hwui/apex/LayoutlibLoader.cpp @@ -47,6 +47,7 @@ extern int register_android_graphics_MaskFilter(JNIEnv* env); extern int register_android_graphics_NinePatch(JNIEnv*); extern int register_android_graphics_PathEffect(JNIEnv* env); extern int register_android_graphics_Shader(JNIEnv* env); +extern int register_android_graphics_RenderEffect(JNIEnv* env); extern int register_android_graphics_Typeface(JNIEnv* env); namespace android { @@ -108,6 +109,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)}, // {"android.graphics.Region", REG_JNI(register_android_graphics_Region)}, {"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)}, + {"android.graphics.RenderEffect", REG_JNI(register_android_graphics_RenderEffect)}, {"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)}, {"android.graphics.animation.NativeInterpolatorFactory", REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory)}, diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp index 12e2e8135278..e1f5abd786bf 100644 --- a/libs/hwui/apex/jni_runtime.cpp +++ b/libs/hwui/apex/jni_runtime.cpp @@ -43,6 +43,7 @@ extern int register_android_graphics_Movie(JNIEnv* env); extern int register_android_graphics_NinePatch(JNIEnv*); extern int register_android_graphics_PathEffect(JNIEnv* env); extern int register_android_graphics_Shader(JNIEnv* env); +extern int register_android_graphics_RenderEffect(JNIEnv* env); extern int register_android_graphics_Typeface(JNIEnv* env); extern int register_android_graphics_YuvImage(JNIEnv* env); @@ -73,6 +74,7 @@ extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env); extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env); extern int register_android_graphics_text_MeasuredText(JNIEnv* env); extern int register_android_graphics_text_LineBreaker(JNIEnv *env); +extern int register_android_graphics_text_TextShaper(JNIEnv *env); extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_view_DisplayListCanvas(JNIEnv* env); @@ -123,6 +125,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_Picture), REG_JNI(register_android_graphics_Region), REG_JNI(register_android_graphics_Shader), + REG_JNI(register_android_graphics_RenderEffect), REG_JNI(register_android_graphics_TextureLayer), REG_JNI(register_android_graphics_Typeface), REG_JNI(register_android_graphics_YuvImage), @@ -137,6 +140,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_pdf_PdfRenderer), REG_JNI(register_android_graphics_text_MeasuredText), REG_JNI(register_android_graphics_text_LineBreaker), + REG_JNI(register_android_graphics_text_TextShaper), REG_JNI(register_android_util_PathParser), REG_JNI(register_android_view_RenderNode), diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 2001b5620f84..8df2770b0157 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -73,7 +73,7 @@ void Canvas::drawTextDecorations(float x, float y, float length, const Paint& pa static void simplifyPaint(int color, Paint* paint) { paint->setColor(color); - paint->setShader((sk_sp<uirenderer::Shader>)nullptr); + paint->setShader(nullptr); paint->setColorFilter(nullptr); paint->setLooper(nullptr); paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize()); diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h index 6cde9c553d8c..a15803ad2dca 100644 --- a/libs/hwui/hwui/MinikinUtils.h +++ b/libs/hwui/hwui/MinikinUtils.h @@ -72,7 +72,7 @@ public: size_t start = 0; size_t nGlyphs = layout.nGlyphs(); for (size_t i = 0; i < nGlyphs; i++) { - const minikin::MinikinFont* nextFont = layout.getFont(i); + const minikin::MinikinFont* nextFont = layout.getFont(i)->typeface().get(); if (i > 0 && nextFont != curFont) { SkFont* skfont = &paint->getSkFont(); MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start)); diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index 0bb689c19079..e75e9e7c6933 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -30,8 +30,6 @@ #include <minikin/FamilyVariant.h> #include <minikin/Hyphenator.h> -#include <shader/Shader.h> - namespace android { class Paint : public SkPaint { @@ -151,14 +149,8 @@ public: // The only respected flags are : [ antialias, dither, filterBitmap ] static uint32_t GetSkPaintJavaFlags(const SkPaint&); static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags); - - void setShader(sk_sp<uirenderer::Shader> shader); private: - - using SkPaint::setShader; - using SkPaint::setImageFilter; - SkFont mFont; sk_sp<SkDrawLooper> mLooper; @@ -177,7 +169,6 @@ private: bool mStrikeThru = false; bool mUnderline = false; bool mDevKern = false; - sk_sp<uirenderer::Shader> mShader; }; } // namespace android diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index 21f60fd7b671..fa2674fc2f5e 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -24,8 +24,7 @@ Paint::Paint() , mWordSpacing(0) , mFontFeatureSettings() , mMinikinLocaleListId(0) - , mFamilyVariant(minikin::FamilyVariant::DEFAULT) - , mShader(nullptr) { + , mFamilyVariant(minikin::FamilyVariant::DEFAULT) { // SkPaint::antialiasing defaults to false, but // SkFont::edging defaults to kAntiAlias. To keep them // insync, we manually set the font to kAilas. @@ -46,8 +45,7 @@ Paint::Paint(const Paint& paint) , mAlign(paint.mAlign) , mStrikeThru(paint.mStrikeThru) , mUnderline(paint.mUnderline) - , mDevKern(paint.mDevKern) - , mShader(paint.mShader){} + , mDevKern(paint.mDevKern) {} Paint::~Paint() {} @@ -67,30 +65,9 @@ Paint& Paint::operator=(const Paint& other) { mStrikeThru = other.mStrikeThru; mUnderline = other.mUnderline; mDevKern = other.mDevKern; - mShader = other.mShader; return *this; } -void Paint::setShader(sk_sp<uirenderer::Shader> shader) { - if (shader) { - // If there is an SkShader compatible shader, apply it - sk_sp<SkShader> skShader = shader->asSkShader(); - if (skShader.get()) { - SkPaint::setShader(skShader); - SkPaint::setImageFilter(nullptr); - } else { - // ... otherwise the specified shader can only be represented as an ImageFilter - SkPaint::setShader(nullptr); - SkPaint::setImageFilter(shader->asSkImageFilter()); - } - } else { - // No shader is provided at all, clear out both the SkShader and SkImageFilter slots - SkPaint::setShader(nullptr); - SkPaint::setImageFilter(nullptr); - } - mShader = shader; -} - bool operator==(const Paint& a, const Paint& b) { return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && a.mFont == b.mFont && diff --git a/libs/hwui/jni/FontUtils.h b/libs/hwui/jni/FontUtils.h index b36b4e60e33a..f93a0daaf748 100644 --- a/libs/hwui/jni/FontUtils.h +++ b/libs/hwui/jni/FontUtils.h @@ -38,6 +38,14 @@ struct FontWrapper { minikin::Font font; }; +// We assume FontWrapper's address is the same as underlying Font's address. +// This assumption is used for looking up Java font object from native address. +// The Font object can be created without Java's Font object but all Java's Font objects point to +// the native FontWrapper. So when looking up Java object from minikin::Layout which gives us Font +// address, we lookup Font Java object from Font address with assumption that it is the same as +// FontWrapper address. +static_assert(offsetof(FontWrapper, font) == 0); + // Utility wrapper for java.util.List class ListHelper { public: diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index f6c8496d84ca..3c86b28262b0 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -47,7 +47,6 @@ #include <minikin/LocaleList.h> #include <minikin/Measurement.h> #include <minikin/MinikinPaint.h> -#include <shader/Shader.h> #include <unicode/utf16.h> #include <cassert> @@ -55,8 +54,6 @@ #include <memory> #include <vector> -using namespace android::uirenderer; - namespace android { static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count, @@ -745,10 +742,11 @@ namespace PaintGlue { return obj->getFillPath(*src, dst) ? JNI_TRUE : JNI_FALSE; } - static void setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) { - auto* paint = reinterpret_cast<Paint*>(objHandle); - auto* shader = reinterpret_cast<Shader*>(shaderHandle); - paint->setShader(sk_ref_sp(shader)); + static jlong setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle); + obj->setShader(sk_ref_sp(shader)); + return reinterpret_cast<jlong>(obj->getShader()); } static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) { @@ -1059,7 +1057,7 @@ static const JNINativeMethod methods[] = { {"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin}, {"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin}, {"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath}, - {"nSetShader","(JJ)V", (void*) PaintGlue::setShader}, + {"nSetShader","(JJ)J", (void*) PaintGlue::setShader}, {"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter}, {"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode}, {"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect}, diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp new file mode 100644 index 000000000000..0ebd0ca720d8 --- /dev/null +++ b/libs/hwui/jni/RenderEffect.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "Bitmap.h" +#include "GraphicsJNI.h" +#include "SkImageFilter.h" +#include "SkImageFilters.h" +#include "graphics_jni_helpers.h" +#include "utils/Blur.h" +#include <utils/Log.h> + +using namespace android::uirenderer; + +static jlong createOffsetEffect( + JNIEnv* env, + jobject, + jfloat offsetX, + jfloat offsetY, + jlong inputFilterHandle +) { + auto* inputFilter = reinterpret_cast<const SkImageFilter*>(inputFilterHandle); + sk_sp<SkImageFilter> offset = SkImageFilters::Offset(offsetX, offsetY, sk_ref_sp(inputFilter)); + return reinterpret_cast<jlong>(offset.release()); +} + +static jlong createBlurEffect(JNIEnv* env , jobject, jfloat radiusX, + jfloat radiusY, jlong inputFilterHandle, jint edgeTreatment) { + auto* inputImageFilter = reinterpret_cast<SkImageFilter*>(inputFilterHandle); + sk_sp<SkImageFilter> blurFilter = + SkImageFilters::Blur( + Blur::convertRadiusToSigma(radiusX), + Blur::convertRadiusToSigma(radiusY), + static_cast<SkTileMode>(edgeTreatment), + sk_ref_sp(inputImageFilter), + nullptr); + return reinterpret_cast<jlong>(blurFilter.release()); +} + +static void RenderEffect_safeUnref(SkImageFilter* filter) { + SkSafeUnref(filter); +} + +static jlong getRenderEffectFinalizer(JNIEnv*, jobject) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&RenderEffect_safeUnref)); +} + +static const JNINativeMethod gRenderEffectMethods[] = { + {"nativeGetFinalizer", "()J", (void*)getRenderEffectFinalizer}, + {"nativeCreateOffsetEffect", "(FFJ)J", (void*)createOffsetEffect}, + {"nativeCreateBlurEffect", "(FFJI)J", (void*)createBlurEffect} +}; + +int register_android_graphics_RenderEffect(JNIEnv* env) { + android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect", + gRenderEffectMethods, NELEM(gRenderEffectMethods)); + return 0; +}
\ No newline at end of file diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index 0a194f9dd666..e76aace601be 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -5,14 +5,6 @@ #include "SkShader.h" #include "SkBlendMode.h" #include "include/effects/SkRuntimeEffect.h" -#include "shader/Shader.h" -#include "shader/BitmapShader.h" -#include "shader/BlurShader.h" -#include "shader/ComposeShader.h" -#include "shader/LinearGradientShader.h" -#include "shader/RadialGradientShader.h" -#include "shader/RuntimeShader.h" -#include "shader/SweepGradientShader.h" #include <vector> @@ -58,7 +50,7 @@ static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvAr /////////////////////////////////////////////////////////////////////////////////////////////// -static void Shader_safeUnref(Shader* shader) { +static void Shader_safeUnref(SkShader* shader) { SkSafeUnref(shader); } @@ -82,15 +74,15 @@ static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, j SkBitmap bitmap; image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode); } + sk_sp<SkShader> shader = image->makeShader( + (SkTileMode)tileModeX, (SkTileMode)tileModeY); + ThrowIAE_IfNull(env, shader.get()); - auto* shader = new BitmapShader( - image, - static_cast<SkTileMode>(tileModeX), - static_cast<SkTileMode>(tileModeY), - matrix - ); + if (matrix) { + shader = shader->makeWithLocalMatrix(*matrix); + } - return reinterpret_cast<jlong>(shader); + return reinterpret_cast<jlong>(shader.release()); } /////////////////////////////////////////////////////////////////////////////////////////////// @@ -126,18 +118,17 @@ static jlong LinearGradient_create(JNIEnv* env, jobject, jlong matrixPtr, #error Need to convert float array to SkScalar array before calling the following function. #endif - auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); - auto* shader = new LinearGradientShader( - pts, - colors, - GraphicsJNI::getNativeColorSpace(colorSpaceHandle), - pos, - static_cast<SkTileMode>(tileMode), - sGradientShaderFlags, - matrix - ); + sk_sp<SkShader> shader(SkGradientShader::MakeLinear(pts, &colors[0], + GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(), + static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr)); + ThrowIAE_IfNull(env, shader); - return reinterpret_cast<jlong>(shader); + const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); + if (matrix) { + shader = shader->makeWithLocalMatrix(*matrix); + } + + return reinterpret_cast<jlong>(shader.release()); } /////////////////////////////////////////////////////////////////////////////////////////////// @@ -157,20 +148,17 @@ static jlong RadialGradient_create(JNIEnv* env, jobject, jlong matrixPtr, jfloat #error Need to convert float array to SkScalar array before calling the following function. #endif - auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); + sk_sp<SkShader> shader = SkGradientShader::MakeRadial(center, radius, &colors[0], + GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(), + static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr); + ThrowIAE_IfNull(env, shader); - auto* shader = new RadialGradientShader( - center, - radius, - colors, - GraphicsJNI::getNativeColorSpace(colorSpaceHandle), - pos, - static_cast<SkTileMode>(tileMode), - sGradientShaderFlags, - matrix - ); + const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); + if (matrix) { + shader = shader->makeWithLocalMatrix(*matrix); + } - return reinterpret_cast<jlong>(shader); + return reinterpret_cast<jlong>(shader.release()); } /////////////////////////////////////////////////////////////////////////////// @@ -186,75 +174,54 @@ static jlong SweepGradient_create(JNIEnv* env, jobject, jlong matrixPtr, jfloat #error Need to convert float array to SkScalar array before calling the following function. #endif - auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); + sk_sp<SkShader> shader = SkGradientShader::MakeSweep(x, y, &colors[0], + GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(), + sGradientShaderFlags, nullptr); + ThrowIAE_IfNull(env, shader); - auto* shader = new SweepGradientShader( - x, - y, - colors, - GraphicsJNI::getNativeColorSpace(colorSpaceHandle), - pos, - sGradientShaderFlags, - matrix - ); + const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); + if (matrix) { + shader = shader->makeWithLocalMatrix(*matrix); + } - return reinterpret_cast<jlong>(shader); + return reinterpret_cast<jlong>(shader.release()); } /////////////////////////////////////////////////////////////////////////////////////////////// static jlong ComposeShader_create(JNIEnv* env, jobject o, jlong matrixPtr, jlong shaderAHandle, jlong shaderBHandle, jint xfermodeHandle) { - auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); - auto* shaderA = reinterpret_cast<Shader*>(shaderAHandle); - auto* shaderB = reinterpret_cast<Shader*>(shaderBHandle); - - auto mode = static_cast<SkBlendMode>(xfermodeHandle); - - auto* composeShader = new ComposeShader( - *shaderA, - *shaderB, - mode, - matrix - ); - - return reinterpret_cast<jlong>(composeShader); -} - -/////////////////////////////////////////////////////////////////////////////////////////////// - -static jlong BlurShader_create(JNIEnv* env , jobject o, jlong matrixPtr, jfloat sigmaX, - jfloat sigmaY, jlong shaderHandle, jint edgeTreatment) { - auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); - auto* inputShader = reinterpret_cast<Shader*>(shaderHandle); - - auto* blurShader = new BlurShader( - sigmaX, - sigmaY, - inputShader, - static_cast<SkTileMode>(edgeTreatment), - matrix - ); - return reinterpret_cast<jlong>(blurShader); + const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); + SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle); + SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle); + SkBlendMode mode = static_cast<SkBlendMode>(xfermodeHandle); + sk_sp<SkShader> baseShader(SkShaders::Blend(mode, + sk_ref_sp(shaderA), sk_ref_sp(shaderB))); + + SkShader* shader; + + if (matrix) { + shader = baseShader->makeWithLocalMatrix(*matrix).release(); + } else { + shader = baseShader.release(); + } + return reinterpret_cast<jlong>(shader); } /////////////////////////////////////////////////////////////////////////////////////////////// static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderFactory, jlong matrixPtr, jbyteArray inputs, jlong colorSpaceHandle, jboolean isOpaque) { - auto* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory); + SkRuntimeEffect* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory); AutoJavaByteArray arInputs(env, inputs); - auto data = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length()); - auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); + sk_sp<SkData> fData; + fData = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length()); + const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); + sk_sp<SkShader> shader = effect->makeShader(fData, nullptr, 0, matrix, isOpaque == JNI_TRUE); + ThrowIAE_IfNull(env, shader); - auto* shader = new RuntimeShader( - *effect, - std::move(data), - isOpaque == JNI_TRUE, - matrix - ); - return reinterpret_cast<jlong>(shader); + return reinterpret_cast<jlong>(shader.release()); } /////////////////////////////////////////////////////////////////////////////////////////////// @@ -272,8 +239,12 @@ static jlong RuntimeShader_createShaderFactory(JNIEnv* env, jobject, jstring sks /////////////////////////////////////////////////////////////////////////////////////////////// +static void Effect_safeUnref(SkRuntimeEffect* effect) { + SkSafeUnref(effect); +} + static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) { - return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref)); + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Effect_safeUnref)); } /////////////////////////////////////////////////////////////////////////////////////////////// @@ -291,10 +262,6 @@ static const JNINativeMethod gBitmapShaderMethods[] = { { "nativeCreate", "(JJII)J", (void*)BitmapShader_constructor }, }; -static const JNINativeMethod gBlurShaderMethods[] = { - { "nativeCreate", "(JFFJI)J", (void*)BlurShader_create } -}; - static const JNINativeMethod gLinearGradientMethods[] = { { "nativeCreate", "(JFFFF[J[FIJ)J", (void*)LinearGradient_create }, }; @@ -326,8 +293,6 @@ int register_android_graphics_Shader(JNIEnv* env) NELEM(gShaderMethods)); android::RegisterMethodsOrDie(env, "android/graphics/BitmapShader", gBitmapShaderMethods, NELEM(gBitmapShaderMethods)); - android::RegisterMethodsOrDie(env, "android/graphics/BlurShader", gBlurShaderMethods, - NELEM(gBlurShaderMethods)); android::RegisterMethodsOrDie(env, "android/graphics/LinearGradient", gLinearGradientMethods, NELEM(gLinearGradientMethods)); android::RegisterMethodsOrDie(env, "android/graphics/RadialGradient", gRadialGradientMethods, diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index 85c802b40459..4b4aa92b97b7 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -215,6 +215,12 @@ static jboolean android_view_RenderNode_setAlpha(CRITICAL_JNI_PARAMS_COMMA jlong return SET_AND_DIRTY(setAlpha, alpha, RenderNode::ALPHA); } +static jboolean android_view_RenderNode_setRenderEffect(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + jlong renderEffectPtr) { + SkImageFilter* imageFilter = reinterpret_cast<SkImageFilter*>(renderEffectPtr); + return SET_AND_DIRTY(mutateLayerProperties().setImageFilter, imageFilter, RenderNode::GENERIC); +} + static jboolean android_view_RenderNode_setHasOverlappingRendering(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, bool hasOverlappingRendering) { return SET_AND_DIRTY(setHasOverlappingRendering, hasOverlappingRendering, @@ -690,6 +696,7 @@ static const JNINativeMethod gMethods[] = { { "nSetRevealClip", "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip }, { "nSetAlpha", "(JF)Z", (void*) android_view_RenderNode_setAlpha }, + { "nSetRenderEffect", "(JJ)V", (void*) android_view_RenderNode_setRenderEffect }, { "nSetHasOverlappingRendering", "(JZ)Z", (void*) android_view_RenderNode_setHasOverlappingRendering }, { "nSetUsageHint", "(JI)V", (void*) android_view_RenderNode_setUsageHint }, diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp index a1adcb30e80d..9cffceb308c8 100644 --- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp +++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp @@ -143,13 +143,13 @@ static void updateFullPathPropertiesAndStrokeStyles(JNIEnv*, jobject, jlong full static void updateFullPathFillGradient(JNIEnv*, jobject, jlong pathPtr, jlong fillGradientPtr) { VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr); - auto* fillShader = reinterpret_cast<Shader*>(fillGradientPtr); + SkShader* fillShader = reinterpret_cast<SkShader*>(fillGradientPtr); path->mutateStagingProperties()->setFillGradient(fillShader); } static void updateFullPathStrokeGradient(JNIEnv*, jobject, jlong pathPtr, jlong strokeGradientPtr) { VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr); - auto* strokeShader = reinterpret_cast<Shader*>(strokeGradientPtr); + SkShader* strokeShader = reinterpret_cast<SkShader*>(strokeGradientPtr); path->mutateStagingProperties()->setStrokeGradient(strokeShader); } diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index bcdb4f5f1d13..0eb409557718 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -198,6 +198,59 @@ static jfloat Font_getFontMetrics(JNIEnv* env, jobject, jlong fontHandle, jlong return spacing; } +// Critical Native +static jlong Font_getFontInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) { + FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle); + MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font.typeface().get()); + + uint64_t result = font->font.style().weight(); + result |= font->font.style().slant() == minikin::FontStyle::Slant::ITALIC ? 0x10000 : 0x00000; + result |= ((static_cast<uint64_t>(minikinSkia->GetFontIndex())) << 32); + result |= ((static_cast<uint64_t>(minikinSkia->GetAxes().size())) << 48); + return result; +} + +// Critical Native +static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle, jint index) { + FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle); + MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font.typeface().get()); + const minikin::FontVariation& var = minikinSkia->GetAxes().at(index); + uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value); + return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary); +} + +/////////////////////////////////////////////////////////////////////////////// + +struct FontBufferWrapper { + FontBufferWrapper(const std::shared_ptr<minikin::MinikinFont>& font) : minikinFont(font) {} + // MinikinFont holds a shared pointer of SkTypeface which has reference to font data. + std::shared_ptr<minikin::MinikinFont> minikinFont; +}; + +static void unrefBuffer(jlong nativePtr) { + FontBufferWrapper* wrapper = reinterpret_cast<FontBufferWrapper*>(nativePtr); + delete wrapper; +} + +// Critical Native +static jlong FontBufferHelper_refFontBuffer(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) { + FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle); + return reinterpret_cast<jlong>(new FontBufferWrapper(font->font.typeface())); +} + +// Fast Native +static jobject FontBufferHelper_wrapByteBuffer(JNIEnv* env, jobject, jlong nativePtr) { + FontBufferWrapper* wrapper = reinterpret_cast<FontBufferWrapper*>(nativePtr); + return env->NewDirectByteBuffer( + const_cast<void*>(wrapper->minikinFont->GetFontData()), + wrapper->minikinFont->GetFontSize()); +} + +// Critical Native +static jlong FontBufferHelper_getReleaseFunc(CRITICAL_JNI_PARAMS) { + return reinterpret_cast<jlong>(unrefBuffer); +} + /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gFontBuilderMethods[] = { @@ -211,13 +264,23 @@ static const JNINativeMethod gFontBuilderMethods[] = { static const JNINativeMethod gFontMethods[] = { { "nGetGlyphBounds", "(JIJLandroid/graphics/RectF;)F", (void*) Font_getGlyphBounds }, { "nGetFontMetrics", "(JJLandroid/graphics/Paint$FontMetrics;)F", (void*) Font_getFontMetrics }, + { "nGetFontInfo", "(J)J", (void*) Font_getFontInfo }, + { "nGetAxisInfo", "(JI)J", (void*) Font_getAxisInfo }, +}; + +static const JNINativeMethod gFontBufferHelperMethods[] = { + { "nRefFontBuffer", "(J)J", (void*) FontBufferHelper_refFontBuffer }, + { "nWrapByteBuffer", "(J)Ljava/nio/ByteBuffer;", (void*) FontBufferHelper_wrapByteBuffer }, + { "nGetReleaseFunc", "()J", (void*) FontBufferHelper_getReleaseFunc }, }; int register_android_graphics_fonts_Font(JNIEnv* env) { return RegisterMethodsOrDie(env, "android/graphics/fonts/Font$Builder", gFontBuilderMethods, NELEM(gFontBuilderMethods)) + RegisterMethodsOrDie(env, "android/graphics/fonts/Font", gFontMethods, - NELEM(gFontMethods)); + NELEM(gFontMethods)) + + RegisterMethodsOrDie(env, "android/graphics/fonts/NativeFontBufferHelper", + gFontBufferHelperMethods, NELEM(gFontBufferHelperMethods)); } } diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp new file mode 100644 index 000000000000..9d9e91f19851 --- /dev/null +++ b/libs/hwui/jni/text/TextShaper.cpp @@ -0,0 +1,206 @@ +/* + * 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. + */ + +#undef LOG_TAG +#define LOG_TAG "TextShaper" + +#include "graphics_jni_helpers.h" +#include <nativehelper/ScopedStringChars.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include <set> +#include <algorithm> + +#include "SkPaint.h" +#include "SkTypeface.h" +#include <hwui/MinikinSkia.h> +#include <hwui/MinikinUtils.h> +#include <hwui/Paint.h> +#include <minikin/MinikinPaint.h> +#include <minikin/MinikinFont.h> + +namespace android { + +struct LayoutWrapper { + LayoutWrapper(minikin::Layout&& layout, float ascent, float descent) + : layout(std::move(layout)), ascent(ascent), descent(descent) {} + minikin::Layout layout; + float ascent; + float descent; +}; + +static void releaseLayout(jlong ptr) { + delete reinterpret_cast<LayoutWrapper*>(ptr); +} + +static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int count, + int contextStart, int contextCount, minikin::Bidi bidiFlags, + const Paint& paint, const Typeface* typeface) { + + minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(&paint, typeface); + + minikin::Layout layout = MinikinUtils::doLayout(&paint, bidiFlags, typeface, + text, textSize, start, count, contextStart, contextCount, nullptr); + + std::set<const minikin::Font*> seenFonts; + float overallAscent = 0; + float overallDescent = 0; + for (int i = 0; i < layout.nGlyphs(); ++i) { + const minikin::Font* font = layout.getFont(i); + if (seenFonts.find(font) != seenFonts.end()) continue; + minikin::MinikinExtent extent = {}; + font->typeface()->GetFontExtent(&extent, minikinPaint, layout.getFakery(i)); + overallAscent = std::min(overallAscent, extent.ascent); + overallDescent = std::max(overallDescent, extent.descent); + } + + std::unique_ptr<LayoutWrapper> ptr = std::make_unique<LayoutWrapper>( + std::move(layout), overallAscent, overallDescent + ); + + return reinterpret_cast<jlong>(ptr.release()); +} + +static jlong TextShaper_shapeTextRunChars(JNIEnv *env, jobject, jcharArray charArray, + jint start, jint count, jint contextStart, jint contextCount, jboolean isRtl, + jlong paintPtr) { + ScopedCharArrayRO text(env, charArray); + Paint* paint = reinterpret_cast<Paint*>(paintPtr); + const Typeface* typeface = paint->getAndroidTypeface(); + const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; + return shapeTextRun( + text.get(), text.size(), + start, count, + contextStart, contextCount, + bidiFlags, + *paint, typeface); + +} + +static jlong TextShaper_shapeTextRunString(JNIEnv *env, jobject, jstring string, + jint start, jint count, jint contextStart, jint contextCount, jboolean isRtl, + jlong paintPtr) { + ScopedStringChars text(env, string); + Paint* paint = reinterpret_cast<Paint*>(paintPtr); + const Typeface* typeface = paint->getAndroidTypeface(); + const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; + return shapeTextRun( + text.get(), text.size(), + start, count, + contextStart, contextCount, + bidiFlags, + *paint, typeface); +} + +// CriticalNative +static jint TextShaper_Result_getGlyphCount(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.nGlyphs(); +} + +// CriticalNative +static jfloat TextShaper_Result_getTotalAdvance(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getAdvance(); +} + +// CriticalNative +static jfloat TextShaper_Result_getAscent(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->ascent; +} + +// CriticalNative +static jfloat TextShaper_Result_getDescent(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->descent; +} + +// CriticalNative +static jint TextShaper_Result_getGlyphId(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getGlyphId(i); +} + +// CriticalNative +static jfloat TextShaper_Result_getX(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getX(i); +} + +// CriticalNative +static jfloat TextShaper_Result_getY(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getY(i); +} + +// CriticalNative +static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return reinterpret_cast<jlong>(layout->layout.getFont(i)); +} + +// CriticalNative +static jlong TextShaper_Result_nReleaseFunc(CRITICAL_JNI_PARAMS) { + return reinterpret_cast<jlong>(releaseLayout); +} + +static const JNINativeMethod gMethods[] = { + // Fast Natives + {"nativeShapeTextRun", "(" + "[C" // text + "I" // start + "I" // count + "I" // contextStart + "I" // contextCount + "Z" // isRtl + "J)" // paint + "J", // LayoutPtr + (void*) TextShaper_shapeTextRunChars}, + + {"nativeShapeTextRun", "(" + "Ljava/lang/String;" // text + "I" // start + "I" // count + "I" // contextStart + "I" // contextCount + "Z" // isRtl + "J)" // paint + "J", // LayoutPtr + (void*) TextShaper_shapeTextRunString}, + +}; + +static const JNINativeMethod gResultMethods[] = { + { "nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount }, + { "nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance }, + { "nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent }, + { "nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent }, + { "nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId }, + { "nGetX", "(JI)F", (void*)TextShaper_Result_getX }, + { "nGetY", "(JI)F", (void*)TextShaper_Result_getY }, + { "nGetFont", "(JI)J", (void*)TextShaper_Result_getFont }, + { "nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc }, +}; + +int register_android_graphics_text_TextShaper(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/text/TextShaper", gMethods, + NELEM(gMethods)) + + RegisterMethodsOrDie(env, "android/graphics/text/PositionedGlyphs", + gResultMethods, NELEM(gResultMethods)); +} + +} + diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 00ceb2d84f9e..1473b3e5abb7 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -172,10 +172,12 @@ static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultip SkPaint* paint) { paint->setFilterQuality(kLow_SkFilterQuality); if (alphaMultiplier < 1.0f || properties.alpha() < 255 || - properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr) { + properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr || + properties.getImageFilter() != nullptr) { paint->setAlpha(properties.alpha() * alphaMultiplier); paint->setBlendMode(properties.xferMode()); paint->setColorFilter(sk_ref_sp(properties.getColorFilter())); + paint->setImageFilter(sk_ref_sp(properties.getImageFilter())); return true; } return false; diff --git a/libs/hwui/shader/ComposeShader.cpp b/libs/hwui/shader/ComposeShader.cpp deleted file mode 100644 index 3765489e7431..000000000000 --- a/libs/hwui/shader/ComposeShader.cpp +++ /dev/null @@ -1,51 +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. - */ - -#include "ComposeShader.h" - -#include "SkImageFilters.h" -#include "SkShader.h" - -namespace android::uirenderer { - -ComposeShader::ComposeShader(Shader& shaderA, Shader& shaderB, const SkBlendMode blendMode, - const SkMatrix* matrix) - : Shader(matrix) { - // If both Shaders can be represented as SkShaders then use those, if not - // create an SkImageFilter from both Shaders and create the equivalent SkImageFilter - sk_sp<SkShader> skShaderA = shaderA.asSkShader(); - sk_sp<SkShader> skShaderB = shaderB.asSkShader(); - if (skShaderA.get() && skShaderB.get()) { - skShader = SkShaders::Blend(blendMode, skShaderA, skShaderB); - skImageFilter = nullptr; - } else { - sk_sp<SkImageFilter> skImageFilterA = shaderA.asSkImageFilter(); - sk_sp<SkImageFilter> skImageFilterB = shaderB.asSkImageFilter(); - skShader = nullptr; - skImageFilter = SkImageFilters::Xfermode(blendMode, skImageFilterA, skImageFilterB); - } -} - -sk_sp<SkShader> ComposeShader::makeSkShader() { - return skShader; -} - -sk_sp<SkImageFilter> ComposeShader::makeSkImageFilter() { - return skImageFilter; -} - -ComposeShader::~ComposeShader() {} -} // namespace android::uirenderer diff --git a/libs/hwui/shader/ComposeShader.h b/libs/hwui/shader/ComposeShader.h deleted file mode 100644 index a246b520d46a..000000000000 --- a/libs/hwui/shader/ComposeShader.h +++ /dev/null @@ -1,43 +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. - */ - -#pragma once - -#include "Shader.h" -#include "SkShader.h" - -namespace android::uirenderer { - -/** - * Shader implementation that can composite 2 Shaders together with the specified blend mode. - * This implementation can appropriately convert the composed result to either an SkShader or - * SkImageFilter depending on the inputs - */ -class ComposeShader : public Shader { -public: - ComposeShader(Shader& shaderA, Shader& shaderB, const SkBlendMode blendMode, - const SkMatrix* matrix); - ~ComposeShader() override; - -protected: - sk_sp<SkShader> makeSkShader() override; - sk_sp<SkImageFilter> makeSkImageFilter() override; - -private: - sk_sp<SkShader> skShader; - sk_sp<SkImageFilter> skImageFilter; -}; -} // namespace android::uirenderer diff --git a/libs/hwui/shader/LinearGradientShader.cpp b/libs/hwui/shader/LinearGradientShader.cpp deleted file mode 100644 index 868fa44fb4b7..000000000000 --- a/libs/hwui/shader/LinearGradientShader.cpp +++ /dev/null @@ -1,39 +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. - */ - -#include "LinearGradientShader.h" - -#include <vector> - -#include "SkGradientShader.h" - -namespace android::uirenderer { - -LinearGradientShader::LinearGradientShader(const SkPoint pts[2], - const std::vector<SkColor4f>& colors, - sk_sp<SkColorSpace> colorspace, const SkScalar pos[], - const SkTileMode tileMode, const uint32_t shaderFlags, - const SkMatrix* matrix) - : Shader(matrix) - , skShader(SkGradientShader::MakeLinear(pts, colors.data(), colorspace, pos, colors.size(), - tileMode, shaderFlags, nullptr)) {} - -sk_sp<SkShader> LinearGradientShader::makeSkShader() { - return skShader; -} - -LinearGradientShader::~LinearGradientShader() {} -} // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/shader/LinearGradientShader.h b/libs/hwui/shader/LinearGradientShader.h deleted file mode 100644 index 596f4e009448..000000000000 --- a/libs/hwui/shader/LinearGradientShader.h +++ /dev/null @@ -1,42 +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. - */ - -#pragma once - -#include "Shader.h" -#include "SkShader.h" - -namespace android::uirenderer { - -/** - * Shader implementation that renders a color ramp of colors to either as either SkShader or - * SkImageFilter - */ -class LinearGradientShader : public Shader { -public: - LinearGradientShader(const SkPoint pts[2], const std::vector<SkColor4f>& colors, - sk_sp<SkColorSpace> colorspace, const SkScalar pos[], - const SkTileMode tileMode, const uint32_t shaderFlags, - const SkMatrix* matrix); - ~LinearGradientShader() override; - -protected: - sk_sp<SkShader> makeSkShader() override; - -private: - sk_sp<SkShader> skShader; -}; -} // namespace android::uirenderer diff --git a/libs/hwui/shader/RadialGradientShader.cpp b/libs/hwui/shader/RadialGradientShader.cpp deleted file mode 100644 index 21ff56fee2f8..000000000000 --- a/libs/hwui/shader/RadialGradientShader.cpp +++ /dev/null @@ -1,38 +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. - */ -#include "RadialGradientShader.h" - -#include <vector> - -#include "SkGradientShader.h" - -namespace android::uirenderer { - -RadialGradientShader::RadialGradientShader(const SkPoint& center, const float radius, - const std::vector<SkColor4f>& colors, - sk_sp<SkColorSpace> colorspace, const SkScalar pos[], - const SkTileMode tileMode, const uint32_t shaderFlags, - const SkMatrix* matrix) - : Shader(matrix) - , skShader(SkGradientShader::MakeRadial(center, radius, colors.data(), colorspace, pos, - colors.size(), tileMode, shaderFlags, nullptr)) {} - -sk_sp<SkShader> RadialGradientShader::makeSkShader() { - return skShader; -} - -RadialGradientShader::~RadialGradientShader() {} -} // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/shader/RadialGradientShader.h b/libs/hwui/shader/RadialGradientShader.h deleted file mode 100644 index 9a2ff139aedb..000000000000 --- a/libs/hwui/shader/RadialGradientShader.h +++ /dev/null @@ -1,42 +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. - */ - -#pragma once - -#include "Shader.h" -#include "SkShader.h" - -namespace android::uirenderer { - -/** - * Shader implementation that renders a color ramp from the center outward to either as either - * a SkShader or SkImageFilter - */ -class RadialGradientShader : public Shader { -public: - RadialGradientShader(const SkPoint& center, const float radius, - const std::vector<SkColor4f>& colors, sk_sp<SkColorSpace> colorSpace, - const SkScalar pos[], const SkTileMode tileMode, const uint32_t shaderFlags, - const SkMatrix* matrix); - ~RadialGradientShader() override; - -protected: - sk_sp<SkShader> makeSkShader() override; - -private: - sk_sp<SkShader> skShader; -}; -} // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/shader/RuntimeShader.cpp b/libs/hwui/shader/RuntimeShader.cpp deleted file mode 100644 index dd0b6980841a..000000000000 --- a/libs/hwui/shader/RuntimeShader.cpp +++ /dev/null @@ -1,36 +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. - */ - -#include "RuntimeShader.h" - -#include "SkShader.h" -#include "include/effects/SkRuntimeEffect.h" - -namespace android::uirenderer { - -RuntimeShader::RuntimeShader(SkRuntimeEffect& effect, sk_sp<SkData> data, bool isOpaque, - const SkMatrix* matrix) - : Shader(nullptr) - , // Explicitly passing null as RuntimeShader is created with the - // matrix directly - skShader(effect.makeShader(std::move(data), nullptr, 0, matrix, isOpaque)) {} - -sk_sp<SkShader> RuntimeShader::makeSkShader() { - return skShader; -} - -RuntimeShader::~RuntimeShader() {} -} // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/shader/Shader.cpp b/libs/hwui/shader/Shader.cpp deleted file mode 100644 index 45123dd55002..000000000000 --- a/libs/hwui/shader/Shader.cpp +++ /dev/null @@ -1,87 +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. - */ - -#include "Shader.h" - -#include "SkImageFilters.h" -#include "SkPaint.h" -#include "SkRefCnt.h" - -namespace android::uirenderer { - -Shader::Shader(const SkMatrix* matrix) - : localMatrix(matrix ? *matrix : SkMatrix::I()) - , skShader(nullptr) - , skImageFilter(nullptr) {} - -Shader::~Shader() {} - -sk_sp<SkShader> Shader::asSkShader() { - // If we already have created a shader with these parameters just return the existing - // shader we have already created - if (!this->skShader.get()) { - this->skShader = makeSkShader(); - if (this->skShader.get()) { - if (!localMatrix.isIdentity()) { - this->skShader = this->skShader->makeWithLocalMatrix(localMatrix); - } - } - } - return this->skShader; -} - -/** - * By default return null as we cannot convert all visual effects to SkShader instances - */ -sk_sp<SkShader> Shader::makeSkShader() { - return nullptr; -} - -sk_sp<SkImageFilter> Shader::asSkImageFilter() { - // If we already have created an ImageFilter with these parameters just return the existing - // ImageFilter we have already created - if (!this->skImageFilter.get()) { - // Attempt to create an SkImageFilter from the current Shader implementation - this->skImageFilter = makeSkImageFilter(); - if (this->skImageFilter) { - if (!localMatrix.isIdentity()) { - // If we have created an SkImageFilter and we have a transformation, wrap - // the created SkImageFilter to apply the given matrix - this->skImageFilter = SkImageFilters::MatrixTransform( - localMatrix, kMedium_SkFilterQuality, this->skImageFilter); - } - } else { - // Otherwise if no SkImageFilter implementation is provided, create one from - // the result of asSkShader. Note the matrix is already applied to the shader in - // this case so just convert it to an SkImageFilter using SkImageFilters::Paint - SkPaint paint; - paint.setShader(asSkShader()); - sk_sp<SkImageFilter> paintFilter = SkImageFilters::Paint(paint); - this->skImageFilter = SkImageFilters::Xfermode(SkBlendMode::kDstIn, - std::move(paintFilter)); - } - } - return this->skImageFilter; -} - -/** - * By default return null for subclasses to implement. If there is not a direct SkImageFilter - * conversion - */ -sk_sp<SkImageFilter> Shader::makeSkImageFilter() { - return nullptr; -} -} // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/shader/Shader.h b/libs/hwui/shader/Shader.h deleted file mode 100644 index 6403e1147ded..000000000000 --- a/libs/hwui/shader/Shader.h +++ /dev/null @@ -1,84 +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. - */ - -#pragma once - -#include "SkImageFilter.h" -#include "SkShader.h" -#include "SkPaint.h" -#include "SkRefCnt.h" - -class SkMatrix; - -namespace android::uirenderer { - -/** - * Shader class that can optionally wrap an SkShader or SkImageFilter depending - * on the implementation - */ -class Shader: public SkRefCnt { -public: - /** - * Creates a Shader instance with an optional transformation matrix. The transformation matrix - * is copied internally and ownership is unchanged. It is the responsibility of the caller to - * deallocate it appropriately. - * @param matrix Optional matrix to transform the underlying SkShader or SkImageFilter - */ - Shader(const SkMatrix* matrix); - virtual ~Shader(); - - /** - * Create an SkShader from the current Shader instance or return a previously - * created instance. This can be null if no SkShader could be created from this - * Shader instance. - */ - sk_sp<SkShader> asSkShader(); - - /** - * Create an SkImageFilter from the current Shader instance or return a previously - * created instance. Unlike asSkShader, this method cannot return null. - */ - sk_sp<SkImageFilter> asSkImageFilter(); - -protected: - /** - * Create a new SkShader instance based on this Shader instance - */ - virtual sk_sp<SkShader> makeSkShader(); - - /** - * Create a new SkImageFilter instance based on this Shader instance. If no SkImageFilter - * can be created then return nullptr - */ - virtual sk_sp<SkImageFilter> makeSkImageFilter(); - -private: - /** - * Optional matrix transform - */ - const SkMatrix localMatrix; - - /** - * Cached SkShader instance to be returned on subsequent queries - */ - sk_sp<SkShader> skShader; - - /** - * Cached SkImageFilter instance to be returned on subsequent queries - */ - sk_sp<SkImageFilter> skImageFilter; -}; -} // namespace android::uirenderer diff --git a/libs/hwui/shader/SweepGradientShader.cpp b/libs/hwui/shader/SweepGradientShader.cpp deleted file mode 100644 index 3b1f37f8b051..000000000000 --- a/libs/hwui/shader/SweepGradientShader.cpp +++ /dev/null @@ -1,38 +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. - */ - -#include "SweepGradientShader.h" - -#include <vector> - -#include "SkGradientShader.h" -#include "SkImageFilters.h" - -namespace android::uirenderer { - -SweepGradientShader::SweepGradientShader(float x, float y, const std::vector<SkColor4f>& colors, - const sk_sp<SkColorSpace>& colorspace, const SkScalar pos[], - const uint32_t shaderFlags, const SkMatrix* matrix) - : Shader(matrix) - , skShader(SkGradientShader::MakeSweep(x, y, colors.data(), colorspace, pos, colors.size(), - shaderFlags, nullptr)) {} - -sk_sp<SkShader> SweepGradientShader::makeSkShader() { - return skShader; -} - -SweepGradientShader::~SweepGradientShader() {} -} // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/shader/SweepGradientShader.h b/libs/hwui/shader/SweepGradientShader.h deleted file mode 100644 index dad3ef0ffad4..000000000000 --- a/libs/hwui/shader/SweepGradientShader.h +++ /dev/null @@ -1,41 +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. - */ - -#pragma once - -#include "Shader.h" -#include "SkShader.h" - -namespace android::uirenderer { - -/** - * Shader implementation that renders a color ramp clockwise such that the start and end colors - * are visible at 3 o'clock. This handles converting to either an SkShader or SkImageFilter - */ -class SweepGradientShader : public Shader { -public: - SweepGradientShader(float x, float y, const std::vector<SkColor4f>& colors, - const sk_sp<SkColorSpace>& colorspace, const SkScalar pos[], - const uint32_t shaderFlags, const SkMatrix* matrix); - virtual ~SweepGradientShader() override; - -protected: - virtual sk_sp<SkShader> makeSkShader() override; - -private: - sk_sp<SkShader> skShader; -}; -} // namespace android::uirenderer diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp index e2c1651d823a..c4067af388e3 100644 --- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp +++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp @@ -18,7 +18,6 @@ #include "hwui/Paint.h" #include "TestSceneBase.h" #include "tests/common/BitmapAllocationTestUtils.h" -#include <shader/BitmapShader.h> #include "utils/Color.h" class BitmapShaders; @@ -46,24 +45,15 @@ public: }); Paint paint; - sk_sp<BitmapShader> bitmapShader = sk_make_sp<BitmapShader>( - hwuiBitmap->makeImage(), - SkTileMode::kRepeat, - SkTileMode::kRepeat, - nullptr - ); - sk_sp<SkImage> image = hwuiBitmap->makeImage(); - paint.setShader(std::move(bitmapShader)); + sk_sp<SkShader> repeatShader = + image->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat); + paint.setShader(std::move(repeatShader)); canvas.drawRoundRect(0, 0, 500, 500, 50.0f, 50.0f, paint); - sk_sp<BitmapShader> mirrorBitmapShader = sk_make_sp<BitmapShader>( - image, - SkTileMode::kMirror, - SkTileMode::kMirror, - nullptr - ); - paint.setShader(std::move(mirrorBitmapShader)); + sk_sp<SkShader> mirrorShader = + image->makeShader(SkTileMode::kMirror, SkTileMode::kMirror); + paint.setShader(std::move(mirrorShader)); canvas.drawRoundRect(0, 600, 500, 1100, 50.0f, 50.0f, paint); } diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp index d37bc3c7d37c..5886ea39acce 100644 --- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp +++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp @@ -20,10 +20,6 @@ #include <SkGradientShader.h> #include <SkImagePriv.h> #include <ui/PixelFormat.h> -#include <shader/BitmapShader.h> -#include <shader/LinearGradientShader.h> -#include <shader/RadialGradientShader.h> -#include <shader/ComposeShader.h> class HwBitmapInCompositeShader; @@ -54,41 +50,20 @@ public: pixels[4000 + 4 * i + 3] = 255; } buffer->unlock(); - - sk_sp<BitmapShader> bitmapShader = sk_make_sp<BitmapShader>( - Bitmap::createFrom( - buffer->toAHardwareBuffer(), - SkColorSpace::MakeSRGB() - )->makeImage(), - SkTileMode::kClamp, - SkTileMode::kClamp, - nullptr - ); + sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer->toAHardwareBuffer(), + SkColorSpace::MakeSRGB())); + sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap)); SkPoint center; center.set(50, 50); - - std::vector<SkColor4f> vColors(2); - vColors[0] = SkColors::kBlack; - vColors[1] = SkColors::kWhite; - - sk_sp<RadialGradientShader> radialShader = sk_make_sp<RadialGradientShader>( - center, - 50, - vColors, - SkColorSpace::MakeSRGB(), - nullptr, - SkTileMode::kRepeat, - 0, - nullptr - ); - - sk_sp<ComposeShader> compositeShader = sk_make_sp<ComposeShader>( - *bitmapShader.get(), - *radialShader.get(), - SkBlendMode::kDstATop, - nullptr - ); + SkColor colors[2]; + colors[0] = Color::Black; + colors[1] = Color::White; + sk_sp<SkShader> gradientShader = SkGradientShader::MakeRadial( + center, 50, colors, nullptr, 2, SkTileMode::kRepeat); + + sk_sp<SkShader> compositeShader( + SkShaders::Blend(SkBlendMode::kDstATop, hardwareShader, gradientShader)); Paint paint; paint.setShader(std::move(compositeShader)); diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp index 76e39deedd9a..a9449b62a1f8 100644 --- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp @@ -17,8 +17,7 @@ #include "TestSceneBase.h" #include "tests/common/TestListViewSceneBase.h" #include "hwui/Paint.h" -#include "SkColor.h" -#include <shader/LinearGradientShader.h> +#include <SkGradientShader.h> class ListOfFadedTextAnimation; @@ -43,26 +42,15 @@ class ListOfFadedTextAnimation : public TestListViewSceneBase { pts[0].set(0, 0); pts[1].set(0, 1); + SkColor colors[2] = {Color::Black, Color::Transparent}; + sk_sp<SkShader> s( + SkGradientShader::MakeLinear(pts, colors, NULL, 2, SkTileMode::kClamp)); + SkMatrix matrix; matrix.setScale(1, length); matrix.postRotate(-90); - - std::vector<SkColor4f> vColors(2); - vColors[0] = SkColors::kBlack; - vColors[1] = SkColors::kTransparent; - - sk_sp<LinearGradientShader> linearGradientShader = sk_make_sp<LinearGradientShader>( - pts, - vColors, - SkColorSpace::MakeSRGB(), - nullptr, - SkTileMode::kClamp, - 0, - &matrix - ); - Paint fadingPaint; - fadingPaint.setShader(linearGradientShader); + fadingPaint.setShader(s->makeWithLocalMatrix(matrix)); fadingPaint.setBlendMode(SkBlendMode::kDstOut); canvas.drawRect(0, 0, length, itemHeight, fadingPaint); canvas.restore(); diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp index bdc157f85264..a0bc5aa245d5 100644 --- a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp +++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp @@ -17,7 +17,7 @@ #include "TestSceneBase.h" #include <SkColorMatrixFilter.h> -#include <shader/LinearGradientShader.h> +#include <SkGradientShader.h> class SimpleColorMatrixAnimation; @@ -65,12 +65,9 @@ private: // enough renderer might apply it directly to the paint color) float pos[] = {0, 1}; SkPoint pts[] = {SkPoint::Make(0, 0), SkPoint::Make(width, height)}; - std::vector<SkColor4f> colors(2); - colors[0] = SkColor4f::FromColor(Color::DeepPurple_500); - colors[1] = SkColor4f::FromColor(Color::DeepOrange_500); - paint.setShader(sk_make_sp<LinearGradientShader>( - pts, colors, SkColorSpace::MakeSRGB(), pos, SkTileMode::kClamp, - 0, nullptr)); + SkColor colors[2] = {Color::DeepPurple_500, Color::DeepOrange_500}; + paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2, + SkTileMode::kClamp)); // overdraw several times to emphasize shader cost for (int i = 0; i < 10; i++) { diff --git a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp index 9a15c9d370a4..57a260c8d234 100644 --- a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp +++ b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp @@ -17,7 +17,6 @@ #include "TestSceneBase.h" #include <SkGradientShader.h> -#include <shader/LinearGradientShader.h> class SimpleGradientAnimation; @@ -56,24 +55,9 @@ private: // overdraw several times to emphasize shader cost for (int i = 0; i < 10; i++) { // use i%2 start position to pick 2 color combo with black in it - std::vector<SkColor4f> vColors(2); - vColors[0] = ((i % 2) == 0) ? - SkColor4f::FromColor(Color::Transparent) : - SkColor4f::FromColor(Color::Black); - vColors[1] = (((i + 1) % 2) == 0) ? - SkColor4f::FromColor(Color::Black) : - SkColor4f::FromColor(Color::Cyan_500); - - sk_sp<LinearGradientShader> gradient = sk_make_sp<LinearGradientShader>( - pts, - vColors, - SkColorSpace::MakeSRGB(), - pos, - SkTileMode::kClamp, - 0, - nullptr - ); - paint.setShader(gradient); + SkColor colors[3] = {Color::Transparent, Color::Black, Color::Cyan_500}; + paint.setShader(SkGradientShader::MakeLinear(pts, colors + (i % 2), pos, 2, + SkTileMode::kClamp)); canvas.drawRect(i, i, width, height, paint); } }); diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp index 5e56b26f46f0..6d4c57413f00 100644 --- a/libs/hwui/tests/unit/VectorDrawableTests.cpp +++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp @@ -17,14 +17,9 @@ #include <gtest/gtest.h> #include "PathParser.h" -#include "GraphicsJNI.h" -#include "SkGradientShader.h" -#include "SkShader.h" #include "VectorDrawable.h" #include "utils/MathUtils.h" #include "utils/VectorDrawableUtils.h" -#include <shader/Shader.h> -#include <shader/LinearGradientShader.h> #include <functional> @@ -400,21 +395,7 @@ TEST(VectorDrawable, drawPathWithoutIncrementingShaderRefCount) { bitmap.allocN32Pixels(5, 5, false); SkCanvas canvas(bitmap); - SkPoint pts[2]; - pts[0].set(0, 0); - pts[1].set(0, 0); - - std::vector<SkColor4f> colors(2); - colors[0] = SkColors::kBlack; - colors[1] = SkColors::kBlack; - - sk_sp<LinearGradientShader> shader = sk_sp(new LinearGradientShader(pts, - colors, - SkColorSpace::MakeSRGB(), - nullptr, - SkTileMode::kClamp, - SkGradientShader::kInterpolateColorsInPremul_Flag, - nullptr)); + sk_sp<SkShader> shader = SkShaders::Color(SK_ColorBLACK); // Initial ref count is 1 EXPECT_TRUE(shader->unique()); diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java index 9aa0c870e512..46bd22148fb2 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -16,6 +16,8 @@ package android.location; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; @@ -586,6 +588,11 @@ public class Location implements Parcelable { } /** @hide */ + public long getElapsedRealtimeMillis() { + return NANOSECONDS.toMillis(getElapsedRealtimeNanos()); + } + + /** @hide */ public long getElapsedRealtimeAgeNanos(long referenceRealtimeNs) { return referenceRealtimeNs - mElapsedRealtimeNanos; } @@ -595,6 +602,11 @@ public class Location implements Parcelable { return getElapsedRealtimeAgeNanos(SystemClock.elapsedRealtimeNanos()); } + /** @hide */ + public long getElapsedRealtimeAgeMillis() { + return NANOSECONDS.toMillis(getElapsedRealtimeAgeNanos()); + } + /** * Set the time of this fix, in elapsed real-time since system boot. * diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 6e597b2c1d63..ff004094ec59 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -89,6 +89,16 @@ import java.util.function.Consumer; public class LocationManager { /** + * For apps targeting Android S and above, location clients may receive historical locations + * (from before the present time) under some circumstances. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + public static final long DELIVER_HISTORICAL_LOCATIONS = 73144566L; + + /** * For apps targeting Android R and above, {@link #getProvider(String)} will no longer throw any * security exceptions. * @@ -1256,13 +1266,15 @@ public class LocationManager { * arguments. The same listener may be used across multiple providers with different requests * for each provider. * - * <p>It may take a while to receive the first location update. If an immediate location is - * required, applications may use the {@link #getLastKnownLocation(String)} method. + * <p>It may take some time to receive the first location update depending on the conditions the + * device finds itself in. In order to take advantage of cached locations, application may + * consider using {@link #getLastKnownLocation(String)} or {@link #getCurrentLocation(String, + * LocationRequest, CancellationSignal, Executor, Consumer)} instead. * * <p>See {@link LocationRequest} documentation for an explanation of various request parameters * and how they can affect the received locations. * - * <p> If your application wants to passively observe location updates from any provider, then + * <p>If your application wants to passively observe location updates from all providers, then * use the {@link #PASSIVE_PROVIDER}. This provider does not turn on or modify active location * providers, so you do not need to be as careful about minimum time and minimum distance * parameters. However, if your application performs heavy work on a location update (such as @@ -1271,13 +1283,20 @@ public class LocationManager { * * <p>In case the provider you have selected is disabled, location updates will cease, and a * provider availability update will be sent. As soon as the provider is enabled again, another - * provider availability update will be sent and location updates will immediately resume. + * provider availability update will be sent and location updates will resume. * - * <p> When location callbacks are invoked, the system will hold a wakelock on your + * <p>When location callbacks are invoked, the system will hold a wakelock on your * application's behalf for some period of time, but not indefinitely. If your application * requires a long running wakelock within the location callback, you should acquire it * yourself. * + * <p>Spamming location requests is a drain on system resources, and the system has preventative + * measures in place to ensure that this behavior will never result in more locations than could + * be achieved with a single location request with an equivalent interval that is left in place + * the whole time. As part of this amelioration, applications that target Android S and above + * may receive cached or historical locations through their listener. These locations will never + * be older than the interval of the location request. + * * <p>To unregister for location updates, use {@link #removeUpdates(LocationListener)}. * * @param provider a provider listed by {@link #getAllProviders()} diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index 0521b10a2530..c57794f0f04a 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -27,6 +27,8 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -45,6 +47,17 @@ import java.util.Objects; public final class LocationRequest implements Parcelable { /** + * For apps targeting Android S and above, all LocationRequest objects marked as low power will + * throw exceptions if the caller does not have the LOCATION_HARDWARE permission, instead of + * silently dropping the low power part of the request. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + public static final long LOW_POWER_EXCEPTIONS = 168936375L; + + /** * Represents a passive only request. Such a request will not trigger any active locations or * power usage itself, but may receive locations generated in response to other requests. * diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java index 523a072b957a..dbf4ad0b14f9 100644 --- a/media/java/android/media/MediaMetadata.java +++ b/media/java/android/media/MediaMetadata.java @@ -18,13 +18,13 @@ package android.media; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.StringDef; -import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.browse.MediaBrowser; import android.media.session.MediaController; +import android.media.session.MediaSession; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; @@ -417,14 +417,17 @@ public final class MediaMetadata implements Parcelable { } private final Bundle mBundle; + private final int mBitmapDimensionLimit; private MediaDescription mDescription; - private MediaMetadata(Bundle bundle) { + private MediaMetadata(Bundle bundle, int bitmapDimensionLimit) { mBundle = new Bundle(bundle); + mBitmapDimensionLimit = bitmapDimensionLimit; } private MediaMetadata(Parcel in) { mBundle = in.readBundle(); + mBitmapDimensionLimit = Math.max(in.readInt(), 0); } /** @@ -513,6 +516,22 @@ public final class MediaMetadata implements Parcelable { return bmp; } + /** + * Gets the width/height limit (in pixels) for the bitmaps when this metadata was created. + * This method returns a positive value or zero. + * <p> + * If it returns a positive value, then all the bitmaps in this metadata has width/height + * not greater than this limit. Bitmaps may have been scaled down according to the limit. + * <p> + * If it returns zero, then no scaling down was applied to the bitmaps when this metadata + * was created. + * + * @see Builder#setBitmapDimensionLimit(int) + */ + public @IntRange(from = 0) int getBitmapDimensionLimit() { + return mBitmapDimensionLimit; + } + @Override public int describeContents() { return 0; @@ -521,6 +540,7 @@ public final class MediaMetadata implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeBundle(mBundle); + dest.writeInt(mBitmapDimensionLimit); } /** @@ -718,6 +738,7 @@ public final class MediaMetadata implements Parcelable { */ public static final class Builder { private final Bundle mBundle; + private int mBitmapDimensionLimit; /** * Create an empty Builder. Any field that should be included in the @@ -736,30 +757,7 @@ public final class MediaMetadata implements Parcelable { */ public Builder(MediaMetadata source) { mBundle = new Bundle(source.mBundle); - } - - /** - * Create a Builder using a {@link MediaMetadata} instance to set - * initial values, but replace bitmaps with a scaled down copy if their width (or height) - * is larger than maxBitmapSize. - * - * @param source The original metadata to copy. - * @param maxBitmapSize The maximum height/width for bitmaps contained - * in the metadata. - * @hide - */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public Builder(@NonNull MediaMetadata source, @IntRange(from = 1) int maxBitmapSize) { - this(source); - for (String key : mBundle.keySet()) { - Object value = mBundle.get(key); - if (value != null && value instanceof Bitmap) { - Bitmap bmp = (Bitmap) value; - if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) { - putBitmap(key, scaleBitmap(bmp, maxBitmapSize)); - } - } - } + mBitmapDimensionLimit = source.mBitmapDimensionLimit; } /** @@ -902,9 +900,9 @@ public final class MediaMetadata implements Parcelable { * <li>{@link #METADATA_KEY_DISPLAY_ICON}</li> * </ul> * <p> - * Large bitmaps may be scaled down by the system when - * {@link android.media.session.MediaSession#setMetadata} is called. - * To pass full resolution images {@link Uri Uris} should be used with + * Large bitmaps may be scaled down by the system with + * {@link Builder#setBitmapDimensionLimit(int)} when {@link MediaSession#setMetadata} + * is called. To pass full resolution images {@link Uri Uris} should be used with * {@link #putString}. * * @param key The key for referencing this value @@ -923,18 +921,46 @@ public final class MediaMetadata implements Parcelable { } /** + * Sets the maximum width/height (in pixels) for the bitmaps in the metadata. + * Bitmaps will be replaced with scaled down copies if their width (or height) is + * larger than {@code bitmapDimensionLimit}. + * <p> + * In order to unset the limit, pass zero as {@code bitmapDimensionLimit}. + * + * @param bitmapDimensionLimit The maximum width/height (in pixels) for bitmaps + * contained in the metadata. Pass {@code 0} to unset the limit. + */ + @NonNull + public Builder setBitmapDimensionLimit(int bitmapDimensionLimit) { + mBitmapDimensionLimit = Math.max(bitmapDimensionLimit, 0); + return this; + } + + /** * Creates a {@link MediaMetadata} instance with the specified fields. * * @return The new MediaMetadata instance */ public MediaMetadata build() { - return new MediaMetadata(mBundle); + if (mBitmapDimensionLimit > 0) { + for (String key : mBundle.keySet()) { + Object value = mBundle.get(key); + if (value instanceof Bitmap) { + Bitmap bmp = (Bitmap) value; + if (bmp.getHeight() > mBitmapDimensionLimit + || bmp.getWidth() > mBitmapDimensionLimit) { + putBitmap(key, scaleBitmap(bmp, mBitmapDimensionLimit)); + } + } + } + } + return new MediaMetadata(mBundle, mBitmapDimensionLimit); } - private Bitmap scaleBitmap(Bitmap bmp, int maxSize) { - float maxSizeF = maxSize; - float widthScale = maxSizeF / bmp.getWidth(); - float heightScale = maxSizeF / bmp.getHeight(); + private Bitmap scaleBitmap(Bitmap bmp, int maxDimension) { + float maxDimensionF = maxDimension; + float widthScale = maxDimensionF / bmp.getWidth(); + float heightScale = maxDimensionF / bmp.getHeight(); float scale = Math.min(widthScale, heightScale); int height = (int) (bmp.getHeight() * scale); int width = (int) (bmp.getWidth() * scale); diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index d35bc4176cb3..d02b49697821 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -372,7 +372,7 @@ public class Ringtone { AudioAttributes.toLegacyStreamType(mAudioAttributes)) != 0) { startLocalPlayer(); } - } else if (mAllowRemote && (mRemotePlayer != null)) { + } else if (mAllowRemote && (mRemotePlayer != null) && (mUri != null)) { final Uri canonicalUri = mUri.getCanonicalUri(); final boolean looping; final float volume; diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 8deb0c4451ea..9deeb8fbab16 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -34,6 +34,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.AssetFileDescriptor; import android.database.Cursor; +import android.database.StaleDataException; import android.net.Uri; import android.os.Environment; import android.os.FileUtils; @@ -492,7 +493,12 @@ public class RingtoneManager { public Uri getRingtoneUri(int position) { // use cursor directly instead of requerying it, which could easily // cause position to shuffle. - if (mCursor == null || !mCursor.moveToPosition(position)) { + try { + if (mCursor == null || !mCursor.moveToPosition(position)) { + return null; + } + } catch (StaleDataException | IllegalStateException e) { + Log.e(TAG, "Unexpected Exception has been catched.", e); return null; } @@ -1130,11 +1136,13 @@ public class RingtoneManager { // Try finding the scanned ringtone final String filename = getDefaultRingtoneFilename(type); + final String whichAudio = getQueryStringForType(type); + final String where = MediaColumns.DISPLAY_NAME + "=? AND " + whichAudio + "=?"; final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI; try (Cursor cursor = context.getContentResolver().query(baseUri, new String[] { MediaColumns._ID }, - MediaColumns.DISPLAY_NAME + "=?", - new String[] { filename }, null)) { + where, + new String[] { filename, "1" }, null)) { if (cursor.moveToFirst()) { final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse( ContentUris.withAppendedId(baseUri, cursor.getLong(0))); @@ -1162,4 +1170,13 @@ public class RingtoneManager { default: throw new IllegalArgumentException(); } } + + private static String getQueryStringForType(int type) { + switch (type) { + case TYPE_RINGTONE: return MediaStore.Audio.AudioColumns.IS_RINGTONE; + case TYPE_NOTIFICATION: return MediaStore.Audio.AudioColumns.IS_NOTIFICATION; + case TYPE_ALARM: return MediaStore.Audio.AudioColumns.IS_ALARM; + default: throw new IllegalArgumentException(); + } + } } diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index 24dacc48f28e..3af2e17960ae 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -472,7 +472,8 @@ public final class MediaController { /** * @hide */ - @UnsupportedAppUsage + @UnsupportedAppUsage(publicAlternatives = "Check equality of {@link #getSessionToken() tokens}" + + "instead.") public boolean controlsSameSession(MediaController other) { if (other == null) return false; return mSessionBinder.asBinder() == other.getSessionBinder().asBinder(); diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 624607b61b8f..e17e069d61d0 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -464,7 +464,9 @@ public final class MediaSession { int fields = 0; MediaDescription description = null; if (metadata != null) { - metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build(); + metadata = new MediaMetadata.Builder(metadata) + .setBitmapDimensionLimit(mMaxBitmapSize) + .build(); if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) { duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION); } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 1fbb67260895..4380c134b82b 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -25,6 +25,7 @@ import android.media.tv.ITvInputClient; import android.media.tv.ITvInputHardware; import android.media.tv.ITvInputHardwareCallback; import android.media.tv.ITvInputManagerCallback; +import android.media.tv.TvChannelInfo; import android.media.tv.TvContentRatingSystemInfo; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvInputInfo; @@ -88,6 +89,8 @@ interface ITvInputManager { void timeShiftSetPlaybackParams(in IBinder sessionToken, in PlaybackParams params, int userId); void timeShiftEnablePositionTracking(in IBinder sessionToken, boolean enable, int userId); + List<TvChannelInfo> getCurrentTvChannelInfos(int userId); + // For the recording session void startRecording(in IBinder sessionToken, in Uri programUri, in Bundle params, int userId); void stopRecording(in IBinder sessionToken, int userId); diff --git a/media/java/android/media/tv/ITvInputManagerCallback.aidl b/media/java/android/media/tv/ITvInputManagerCallback.aidl index 0f8bf2fb3a1d..9f80bf52123a 100644 --- a/media/java/android/media/tv/ITvInputManagerCallback.aidl +++ b/media/java/android/media/tv/ITvInputManagerCallback.aidl @@ -16,6 +16,7 @@ package android.media.tv; +import android.media.tv.TvChannelInfo; import android.media.tv.TvInputInfo; /** @@ -28,4 +29,5 @@ oneway interface ITvInputManagerCallback { void onInputUpdated(in String inputId); void onInputStateChanged(in String inputId, int state); void onTvInputInfoUpdated(in TvInputInfo TvInputInfo); + void onCurrentTvChannelInfosUpdated(in List<TvChannelInfo> currentTvChannelInfos); } diff --git a/media/java/android/media/tv/TvChannelInfo.aidl b/media/java/android/media/tv/TvChannelInfo.aidl new file mode 100644 index 000000000000..71cd0a70c9be --- /dev/null +++ b/media/java/android/media/tv/TvChannelInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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.media.tv; + +parcelable TvChannelInfo; diff --git a/media/java/android/media/tv/TvChannelInfo.java b/media/java/android/media/tv/TvChannelInfo.java new file mode 100644 index 000000000000..635b13045921 --- /dev/null +++ b/media/java/android/media/tv/TvChannelInfo.java @@ -0,0 +1,148 @@ +/* + * 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.media.tv; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @hide + */ +public final class TvChannelInfo implements Parcelable { + static final String TAG = "TvChannelInfo"; + public static final int APP_TAG_SELF = 0; + public static final int APP_TYPE_SELF = 1; + public static final int APP_TYPE_SYSTEM = 2; + public static final int APP_TYPE_NON_SYSTEM = 3; + + /** @hide */ + @IntDef(prefix = "APP_TYPE_", value = {APP_TYPE_SELF, APP_TYPE_SYSTEM, APP_TYPE_NON_SYSTEM}) + @Retention(RetentionPolicy.SOURCE) + public @interface AppType {} + + public static final @NonNull Parcelable.Creator<TvChannelInfo> CREATOR = + new Parcelable.Creator<TvChannelInfo>() { + @Override + public TvChannelInfo createFromParcel(Parcel source) { + try { + return new TvChannelInfo(source); + } catch (Exception e) { + Log.e(TAG, "Exception creating TvChannelInfo from parcel", e); + return null; + } + } + + @Override + public TvChannelInfo[] newArray(int size) { + return new TvChannelInfo[size]; + } + }; + + + private final String mInputId; + @Nullable private final Uri mChannelUri; + private final boolean mIsRecordingSession; + private final boolean mIsForeground; + @AppType private final int mAppType; + private final int mAppTag; + + public TvChannelInfo( + String inputId, @Nullable Uri channelUri, boolean isRecordingSession, + boolean isForeground, @AppType int appType, int appTag) { + mInputId = inputId; + mChannelUri = channelUri; + mIsRecordingSession = isRecordingSession; + mIsForeground = isForeground; + mAppType = appType; + mAppTag = appTag; + } + + + private TvChannelInfo(Parcel source) { + mInputId = source.readString(); + String uriString = source.readString(); + mChannelUri = uriString == null ? null : Uri.parse(uriString); + mIsRecordingSession = (source.readInt() == 1); + mIsForeground = (source.readInt() == 1); + mAppType = source.readInt(); + mAppTag = source.readInt(); + } + + public String getInputId() { + return mInputId; + } + + public Uri getChannelUri() { + return mChannelUri; + } + + public boolean isRecordingSession() { + return mIsRecordingSession; + } + + public boolean isForeground() { + return mIsForeground; + } + + /** + * Gets app tag. + * <p>App tag is used to differentiate one app from another. + * {@link #APP_TAG_SELF} is for current app. + */ + public int getAppTag() { + return mAppTag; + } + + @AppType + public int getAppType() { + return mAppType; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mInputId); + String uriString = mChannelUri == null ? null : mChannelUri.toString(); + dest.writeString(uriString); + dest.writeInt(mIsRecordingSession ? 1 : 0); + dest.writeInt(mIsForeground ? 1 : 0); + dest.writeInt(mAppType); + dest.writeInt(mAppTag); + } + + @Override + public String toString() { + return "inputID=" + mInputId + + ";channelUri=" + mChannelUri + + ";isRecording=" + mIsRecordingSession + + ";isForeground=" + mIsForeground + + ";appType=" + mAppType + + ";appTag=" + mAppTag; + } +} diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 98a01a4cb449..d38369fea2b8 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -899,6 +899,10 @@ public final class TvInputManager { */ public void onTvInputInfoUpdated(TvInputInfo inputInfo) { } + + /** @hide */ + public void onCurrentTvChannelInfosUpdated(List<TvChannelInfo> tvChannelInfos) { + } } private static final class TvInputCallbackRecord { @@ -958,6 +962,16 @@ public final class TvInputManager { } }); } + + public void postCurrentTvChannelInfosUpdated( + final List<TvChannelInfo> currentTvChannelInfos) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onCurrentTvChannelInfosUpdated(currentTvChannelInfos); + } + }); + } } /** @@ -1262,6 +1276,15 @@ public final class TvInputManager { } } } + + @Override + public void onCurrentTvChannelInfosUpdated(List<TvChannelInfo> currentTvChannelInfos) { + synchronized (mLock) { + for (TvInputCallbackRecord record : mCallbackRecords) { + record.postCurrentTvChannelInfosUpdated(currentTvChannelInfos); + } + } + } }; try { if (mService != null) { @@ -1953,6 +1976,17 @@ public final class TvInputManager { } /** + * @hide + */ + public List<TvChannelInfo> getCurrentTvChannelInfos() { + try { + return mService.getCurrentTvChannelInfos(mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * The Session provides the per-session functionality of TV inputs. * @hide */ diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 1e9cf2c24c7c..4e27c8e2723d 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -95,6 +95,11 @@ cc_library_shared { "-Wunused", "-Wunreachable-code", ], + + // Workaround Clang LTO crash. + lto: { + never: true, + }, } cc_library_shared { diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp index 5ba5c0159275..40e4c54c2921 100644 --- a/media/jni/audioeffect/Android.bp +++ b/media/jni/audioeffect/Android.bp @@ -28,4 +28,9 @@ cc_library_shared { "-Wunused", "-Wunreachable-code", ], + + // Workaround Clang LTO crash. + lto: { + never: true, + }, } diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt index d46f1d1606ba..3cc1e42e6498 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -21,6 +21,7 @@ package android { field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION"; field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL"; field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS"; + field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS"; field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE"; field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET"; @@ -129,6 +130,7 @@ package android { field public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS"; field public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH"; field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO"; + field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS"; field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"; field public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND"; @@ -6138,6 +6140,7 @@ package android.app { field @NonNull public static final android.os.Parcelable.Creator<android.app.PendingIntent> CREATOR; field public static final int FLAG_CANCEL_CURRENT = 268435456; // 0x10000000 field public static final int FLAG_IMMUTABLE = 67108864; // 0x4000000 + field public static final int FLAG_MUTABLE = 33554432; // 0x2000000 field public static final int FLAG_NO_CREATE = 536870912; // 0x20000000 field public static final int FLAG_ONE_SHOT = 1073741824; // 0x40000000 field public static final int FLAG_UPDATE_CURRENT = 134217728; // 0x8000000 @@ -8783,6 +8786,7 @@ package android.bluetooth { method public void onPhyUpdate(android.bluetooth.BluetoothGatt, int, int, int); method public void onReadRemoteRssi(android.bluetooth.BluetoothGatt, int, int); method public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt, int); + method public void onServiceChanged(@NonNull android.bluetooth.BluetoothGatt); method public void onServicesDiscovered(android.bluetooth.BluetoothGatt, int); } @@ -12233,6 +12237,7 @@ package android.content.pm { field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct"; field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint"; field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt"; + field public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 8; // 0x8 field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2 field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1 field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4 @@ -12342,6 +12347,7 @@ package android.content.pm { field public static final int FLAG_HARD_RESTRICTED = 4; // 0x4 field public static final int FLAG_IMMUTABLY_RESTRICTED = 16; // 0x10 field public static final int FLAG_INSTALLED = 1073741824; // 0x40000000 + field public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 32; // 0x20 field public static final int FLAG_SOFT_RESTRICTED = 8; // 0x8 field public static final int PROTECTION_DANGEROUS = 1; // 0x1 field public static final int PROTECTION_FLAG_APPOP = 64; // 0x40 @@ -16490,6 +16496,23 @@ package android.graphics.pdf { package android.graphics.text { + public class GlyphStyle { + ctor public GlyphStyle(@ColorInt int, @FloatRange(from=0) float, @FloatRange(from=0) float, @FloatRange(from=0) float, int); + ctor public GlyphStyle(@NonNull android.graphics.Paint); + method public void applyToPaint(@NonNull android.graphics.Paint); + method @ColorInt public int getColor(); + method public int getFlags(); + method @FloatRange(from=0) public float getFontSize(); + method @FloatRange(from=0) public float getScaleX(); + method @FloatRange(from=0) public float getSkewX(); + method public void setColor(@ColorInt int); + method public void setFlags(int); + method public void setFontSize(@FloatRange(from=0) float); + method public void setFromPaint(@NonNull android.graphics.Paint); + method public void setScaleX(@FloatRange(from=0) float); + method public void setSkewX(@FloatRange(from=0) float); + } + public class LineBreaker { method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int); field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2 @@ -16550,6 +16573,25 @@ package android.graphics.text { method @NonNull public android.graphics.text.MeasuredText.Builder setComputeLayout(boolean); } + public final class PositionedGlyphs { + method public float getAscent(); + method public float getDescent(); + method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int); + method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int); + method public float getOriginX(); + method public float getOriginY(); + method public float getPositionX(@IntRange(from=0) int); + method public float getPositionY(@IntRange(from=0) int); + method @NonNull public android.graphics.text.GlyphStyle getStyle(); + method public float getTotalAdvance(); + method @IntRange(from=0) public int glyphCount(); + } + + public class TextShaper { + method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull char[], int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint); + method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull CharSequence, int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint); + } + } package android.hardware { @@ -26316,6 +26358,7 @@ package android.media { method public boolean containsKey(String); method public int describeContents(); method public android.graphics.Bitmap getBitmap(String); + method @IntRange(from=0) public int getBitmapDimensionLimit(); method @NonNull public android.media.MediaDescription getDescription(); method public long getLong(String); method public android.media.Rating getRating(String); @@ -26365,6 +26408,7 @@ package android.media { method public android.media.MediaMetadata.Builder putRating(String, android.media.Rating); method public android.media.MediaMetadata.Builder putString(String, String); method public android.media.MediaMetadata.Builder putText(String, CharSequence); + method @NonNull public android.media.MediaMetadata.Builder setBitmapDimensionLimit(int); } @Deprecated public abstract class MediaMetadataEditor { @@ -44233,6 +44277,7 @@ package android.telecom { field public static final int MISSED = 5; // 0x5 field public static final int OTHER = 9; // 0x9 field public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED"; + field public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL"; field public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED"; field public static final String REASON_WIFI_ON_BUT_WFC_OFF = "REASON_WIFI_ON_BUT_WFC_OFF"; field public static final int REJECTED = 6; // 0x6 @@ -44932,6 +44977,7 @@ package android.telephony { field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; + field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int"; field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool"; field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool"; @@ -45126,6 +45172,10 @@ package android.telephony { field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool"; field public static final int SERVICE_CLASS_NONE = 0; // 0x0 field public static final int SERVICE_CLASS_VOICE = 1; // 0x1 + field public static final int USSD_OVER_CS_ONLY = 2; // 0x2 + field public static final int USSD_OVER_CS_PREFERRED = 0; // 0x0 + field public static final int USSD_OVER_IMS_ONLY = 3; // 0x3 + field public static final int USSD_OVER_IMS_PREFERRED = 1; // 0x1 } public static final class CarrierConfigManager.Apn { @@ -45300,9 +45350,9 @@ package android.telephony { field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellInfoWcdma> CREATOR; } - public abstract class CellLocation { - ctor public CellLocation(); - method public static android.telephony.CellLocation getEmpty(); + @Deprecated public abstract class CellLocation { + ctor @Deprecated public CellLocation(); + method @Deprecated public static android.telephony.CellLocation getEmpty(); method @Deprecated public static void requestLocationUpdate(); } @@ -46820,19 +46870,19 @@ package android.telephony { package android.telephony.cdma { - public class CdmaCellLocation extends android.telephony.CellLocation { - ctor public CdmaCellLocation(); - ctor public CdmaCellLocation(android.os.Bundle); - method public static double convertQuartSecToDecDegrees(int); - method public void fillInNotifierBundle(android.os.Bundle); - method public int getBaseStationId(); - method public int getBaseStationLatitude(); - method public int getBaseStationLongitude(); - method public int getNetworkId(); - method public int getSystemId(); - method public void setCellLocationData(int, int, int); - method public void setCellLocationData(int, int, int, int, int); - method public void setStateInvalid(); + @Deprecated public class CdmaCellLocation extends android.telephony.CellLocation { + ctor @Deprecated public CdmaCellLocation(); + ctor @Deprecated public CdmaCellLocation(android.os.Bundle); + method @Deprecated public static double convertQuartSecToDecDegrees(int); + method @Deprecated public void fillInNotifierBundle(android.os.Bundle); + method @Deprecated public int getBaseStationId(); + method @Deprecated public int getBaseStationLatitude(); + method @Deprecated public int getBaseStationLongitude(); + method @Deprecated public int getNetworkId(); + method @Deprecated public int getSystemId(); + method @Deprecated public void setCellLocationData(int, int, int); + method @Deprecated public void setCellLocationData(int, int, int, int, int); + method @Deprecated public void setStateInvalid(); } } @@ -47032,15 +47082,15 @@ package android.telephony.euicc { package android.telephony.gsm { - public class GsmCellLocation extends android.telephony.CellLocation { - ctor public GsmCellLocation(); - ctor public GsmCellLocation(android.os.Bundle); - method public void fillInNotifierBundle(android.os.Bundle); - method public int getCid(); - method public int getLac(); - method public int getPsc(); - method public void setLacAndCid(int, int); - method public void setStateInvalid(); + @Deprecated public class GsmCellLocation extends android.telephony.CellLocation { + ctor @Deprecated public GsmCellLocation(); + ctor @Deprecated public GsmCellLocation(android.os.Bundle); + method @Deprecated public void fillInNotifierBundle(android.os.Bundle); + method @Deprecated public int getCid(); + method @Deprecated public int getLac(); + method @Deprecated public int getPsc(); + method @Deprecated public void setLacAndCid(int, int); + method @Deprecated public void setStateInvalid(); } @Deprecated public final class SmsManager { @@ -48127,6 +48177,10 @@ package android.text { method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean); } + public class StyledTextShaper { + method @NonNull public static java.util.List<android.graphics.text.PositionedGlyphs> shapeText(@NonNull CharSequence, int, int, @NonNull android.text.TextDirectionHeuristic, @NonNull android.text.TextPaint); + } + public interface TextDirectionHeuristic { method public boolean isRtl(char[], int, int); method public boolean isRtl(CharSequence, int, int); @@ -51772,6 +51826,33 @@ package android.view { field public int toolType; } + public interface OnReceiveContentCallback<T extends android.view.View> { + method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull T); + method public boolean onReceiveContent(@NonNull T, @NonNull android.view.OnReceiveContentCallback.Payload); + } + + public static final class OnReceiveContentCallback.Payload { + method @NonNull public android.content.ClipData getClip(); + method @Nullable public android.os.Bundle getExtras(); + method public int getFlags(); + method @Nullable public android.net.Uri getLinkUri(); + method public int getSource(); + field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1 + field public static final int SOURCE_AUTOFILL = 3; // 0x3 + field public static final int SOURCE_CLIPBOARD = 0; // 0x0 + field public static final int SOURCE_DRAG_AND_DROP = 2; // 0x2 + field public static final int SOURCE_INPUT_METHOD = 1; // 0x1 + field public static final int SOURCE_PROCESS_TEXT = 4; // 0x4 + } + + public static final class OnReceiveContentCallback.Payload.Builder { + ctor public OnReceiveContentCallback.Payload.Builder(@NonNull android.content.ClipData, int); + method @NonNull public android.view.OnReceiveContentCallback.Payload build(); + method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setExtras(@Nullable android.os.Bundle); + method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setFlags(int); + method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setLinkUri(@Nullable android.net.Uri); + } + public abstract class OrientationEventListener { ctor public OrientationEventListener(android.content.Context); ctor public OrientationEventListener(android.content.Context, int); @@ -52323,6 +52404,7 @@ package android.view { method @IdRes public int getNextFocusRightId(); method @IdRes public int getNextFocusUpId(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); + method @Nullable public android.view.OnReceiveContentCallback<? extends android.view.View> getOnReceiveContentCallback(); method @ColorInt public int getOutlineAmbientShadowColor(); method public android.view.ViewOutlineProvider getOutlineProvider(); method @ColorInt public int getOutlineSpotShadowColor(); @@ -52674,6 +52756,7 @@ package android.view { method public void setOnHoverListener(android.view.View.OnHoverListener); method public void setOnKeyListener(android.view.View.OnKeyListener); method public void setOnLongClickListener(@Nullable android.view.View.OnLongClickListener); + method public void setOnReceiveContentCallback(@Nullable android.view.OnReceiveContentCallback<? extends android.view.View>); method public void setOnScrollChangeListener(android.view.View.OnScrollChangeListener); method @Deprecated public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener); method public void setOnTouchListener(android.view.View.OnTouchListener); @@ -58936,17 +59019,6 @@ package android.widget { method public android.view.View newGroupView(android.content.Context, android.database.Cursor, boolean, android.view.ViewGroup); } - public interface RichContentReceiver<T extends android.view.View> { - method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(); - method public boolean onReceive(@NonNull T, @NonNull android.content.ClipData, int, int); - field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1 - field public static final int SOURCE_AUTOFILL = 3; // 0x3 - field public static final int SOURCE_CLIPBOARD = 0; // 0x0 - field public static final int SOURCE_DRAG_AND_DROP = 2; // 0x2 - field public static final int SOURCE_INPUT_METHOD = 1; // 0x1 - field public static final int SOURCE_PROCESS_TEXT = 4; // 0x4 - } - public class ScrollView extends android.widget.FrameLayout { ctor public ScrollView(android.content.Context); ctor public ScrollView(android.content.Context, android.util.AttributeSet); @@ -59501,10 +59573,10 @@ package android.widget { method public int getMinWidth(); method public final android.text.method.MovementMethod getMovementMethod(); method public int getOffsetForPosition(float, float); + method @Nullable public android.view.OnReceiveContentCallback<android.widget.TextView> getOnReceiveContentCallback(); method public android.text.TextPaint getPaint(); method public int getPaintFlags(); method public String getPrivateImeOptions(); - method @NonNull public android.widget.RichContentReceiver<android.widget.TextView> getRichContentReceiver(); method public int getSelectionEnd(); method public int getSelectionStart(); method @ColorInt public int getShadowColor(); @@ -59632,7 +59704,6 @@ package android.widget { method public void setPaintFlags(int); method public void setPrivateImeOptions(String); method public void setRawInputType(int); - method public void setRichContentReceiver(@NonNull android.widget.RichContentReceiver<android.widget.TextView>); method public void setScroller(android.widget.Scroller); method public void setSelectAllOnFocus(boolean); method public void setShadowLayer(float, float, float, int); @@ -59673,7 +59744,6 @@ package android.widget { method public void setWidth(int); field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0 field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1 - field @NonNull public static final android.widget.RichContentReceiver<android.widget.TextView> DEFAULT_RICH_CONTENT_RECEIVER; } public enum TextView.BufferType { @@ -59690,6 +59760,12 @@ package android.widget { field @NonNull public static final android.os.Parcelable.Creator<android.widget.TextView.SavedState> CREATOR; } + public class TextViewOnReceiveContentCallback implements android.view.OnReceiveContentCallback<android.widget.TextView> { + ctor public TextViewOnReceiveContentCallback(); + method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull android.widget.TextView); + method public boolean onReceiveContent(@NonNull android.widget.TextView, @NonNull android.view.OnReceiveContentCallback.Payload); + } + public interface ThemedSpinnerAdapter extends android.widget.SpinnerAdapter { method @Nullable public android.content.res.Resources.Theme getDropDownViewTheme(); method public void setDropDownViewTheme(@Nullable android.content.res.Resources.Theme); diff --git a/non-updatable-api/module-lib-current.txt b/non-updatable-api/module-lib-current.txt index 6c0dd3394cd7..11cd6a93910e 100644 --- a/non-updatable-api/module-lib-current.txt +++ b/non-updatable-api/module-lib-current.txt @@ -16,6 +16,14 @@ package android.app { } +package android.app.role { + + public final class RoleManager { + method @Nullable public String getDefaultSmsPackage(int); + } + +} + package android.content.rollback { public class RollbackManagerFrameworkInitializer { @@ -45,10 +53,6 @@ package android.media { field public static final int FLAG_FROM_KEY = 4096; // 0x1000 } - public static final class MediaMetadata.Builder { - ctor public MediaMetadata.Builder(@NonNull android.media.MediaMetadata, @IntRange(from=1) int); - } - } package android.media.session { diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt index 746f8aae7122..7a64d280e75d 100644 --- a/non-updatable-api/system-current.txt +++ b/non-updatable-api/system-current.txt @@ -2097,6 +2097,7 @@ package android.content.pm { field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000 field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4 field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800 + field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000 field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000 field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000 field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40 @@ -9303,10 +9304,6 @@ package android.telecom { method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference); } - public final class DisconnectCause implements android.os.Parcelable { - field public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL"; - } - public abstract class InCallService extends android.app.Service { method @Deprecated public android.telecom.Phone getPhone(); method @Deprecated public void onPhoneCreated(android.telecom.Phone); @@ -9584,16 +9581,12 @@ package android.telephony { method public int getTimeoutSeconds(); method public boolean isEnabled(); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR; - field public static final int ERROR_FDN_CHECK_FAILURE = 2; // 0x2 - field public static final int ERROR_NOT_SUPPORTED = 3; // 0x3 - field public static final int ERROR_UNKNOWN = 1; // 0x1 field public static final int REASON_ALL = 4; // 0x4 field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5 field public static final int REASON_BUSY = 1; // 0x1 field public static final int REASON_NOT_REACHABLE = 3; // 0x3 field public static final int REASON_NO_REPLY = 2; // 0x2 field public static final int REASON_UNCONDITIONAL = 0; // 0x0 - field public static final int SUCCESS = 0; // 0x0 } public final class CallQuality implements android.os.Parcelable { @@ -10336,7 +10329,7 @@ package android.telephony { method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingStatus(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int); @@ -10435,6 +10428,10 @@ package android.telephony { public static interface TelephonyManager.CallForwardingInfoCallback { method public void onCallForwardingInfoAvailable(@NonNull android.telephony.CallForwardingInfo); method public void onError(int); + field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 2; // 0x2 + field public static final int RESULT_ERROR_NOT_SUPPORTED = 3; // 0x3 + field public static final int RESULT_ERROR_UNKNOWN = 1; // 0x1 + field public static final int RESULT_SUCCESS = 0; // 0x0 } public final class UiccAccessRule implements android.os.Parcelable { @@ -11801,11 +11798,12 @@ package android.webkit { } public interface PacProcessor { + method @NonNull public static android.webkit.PacProcessor createInstance(); method @Nullable public String findProxyForUrl(@NonNull String); method @NonNull public static android.webkit.PacProcessor getInstance(); - method @NonNull public static android.webkit.PacProcessor getInstanceForNetwork(@Nullable android.net.Network); method @Nullable public default android.net.Network getNetwork(); method public default void releasePacProcessor(); + method public default void setNetwork(@Nullable android.net.Network); method public boolean setProxyScript(@NonNull String); } @@ -11941,11 +11939,11 @@ package android.webkit { } public interface WebViewFactoryProvider { + method @NonNull public default android.webkit.PacProcessor createPacProcessor(); method public android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess); method public android.webkit.CookieManager getCookieManager(); method public android.webkit.GeolocationPermissions getGeolocationPermissions(); method @NonNull public default android.webkit.PacProcessor getPacProcessor(); - method @NonNull public default android.webkit.PacProcessor getPacProcessorForNetwork(@Nullable android.net.Network); method public android.webkit.ServiceWorkerController getServiceWorkerController(); method public android.webkit.WebViewFactoryProvider.Statics getStatics(); method @Deprecated public android.webkit.TokenBindingService getTokenBindingService(); diff --git a/packages/CarSystemUI/res/drawable/car_ic_settings_icon.xml b/packages/CarSystemUI/res/drawable/car_ic_settings_icon.xml new file mode 100644 index 000000000000..147f5397361e --- /dev/null +++ b/packages/CarSystemUI/res/drawable/car_ic_settings_icon.xml @@ -0,0 +1,26 @@ +<?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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="@*android:dimen/status_bar_system_icon_size" + android:height="@*android:dimen/status_bar_system_icon_size" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@color/system_bar_icon_color" + android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4C9.75,2 9.54,2.18 9.51,2.42L9.13,5.07C8.52,5.32 7.96,5.66 7.44,6.05l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46C2.21,8.95 2.27,9.22 2.46,9.37l2.11,1.65C4.53,11.34 4.5,11.67 4.5,12s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65C9.54,21.82 9.75,22 10,22h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64L19.43,12.98zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5s3.5,1.57 3.5,3.5S13.93,15.5 12,15.5z"/> +</vector> diff --git a/packages/CarSystemUI/res/drawable/car_ic_user_icon.xml b/packages/CarSystemUI/res/drawable/car_ic_user_icon.xml index 1195d05da228..270d932714f3 100644 --- a/packages/CarSystemUI/res/drawable/car_ic_user_icon.xml +++ b/packages/CarSystemUI/res/drawable/car_ic_user_icon.xml @@ -21,5 +21,5 @@ android:viewportHeight="24"> <path android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z" - android:fillColor="@color/system_bar_user_icon_color"/> + android:fillColor="@color/system_bar_icon_color"/> </vector>
\ No newline at end of file diff --git a/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml b/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml index 1b12eb404d5e..51d2b9d3e1e0 100644 --- a/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml +++ b/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml @@ -14,9 +14,20 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid android:color="@color/system_bar_background_pill_color"/> - <corners android:radius="30dp"/> -</shape>
\ No newline at end of file +<layer-list + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <item> + <aapt:attr name="android:drawable"> + <shape android:shape="rectangle"> + <solid android:color="@color/system_bar_background_pill_color"/> + <corners android:radius="30dp"/> + </shape> + </aapt:attr> + </item> + <item> + <aapt:attr name="android:drawable"> + <ripple android:color="?android:attr/colorControlHighlight"/> + </aapt:attr> + </item> +</layer-list> diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml index 07c11c76a4f2..7994b19242cd 100644 --- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml @@ -36,6 +36,7 @@ android:layout_height="match_parent" android:layout_alignParentStart="true" android:layout_marginTop="@dimen/car_padding_2" + android:layout_marginStart="@dimen/car_padding_2" android:layout_centerVertical="true" android:gravity="center_vertical" > @@ -44,7 +45,6 @@ android:layout_height="match_parent" android:background="@drawable/system_bar_background_pill" android:layout_weight="1" - android:layout_marginStart="@dimen/car_padding_2" android:gravity="center_vertical" systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivities$QuickSettingActivity;launchFlags=0x24000000;end"> @@ -53,6 +53,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" + android:layout_marginStart="@dimen/car_padding_2" + android:layout_marginEnd="@dimen/car_padding_2" android:gravity="center_vertical" /> </com.android.systemui.car.navigationbar.CarNavigationButton> diff --git a/packages/CarSystemUI/res/layout/system_icons.xml b/packages/CarSystemUI/res/layout/system_icons.xml index 5c06075e8617..f6ffcc8c16f0 100644 --- a/packages/CarSystemUI/res/layout/system_icons.xml +++ b/packages/CarSystemUI/res/layout/system_icons.xml @@ -20,16 +20,24 @@ android:id="@+id/system_icons" android:layout_width="match_parent" android:layout_height="match_parent" - android:gravity="center_vertical"> + android:gravity="center_vertical" + android:orientation="horizontal"> <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_weight="1" - android:padding="10dp" android:scaleType="fitCenter" android:gravity="center_vertical" android:orientation="horizontal" /> + + <ImageView + android:id="@+id/settingsIcon" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginStart="@dimen/car_padding_2" + android:src="@drawable/car_ic_settings_icon" + /> </LinearLayout>
\ No newline at end of file diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml index 6fe5004c459f..0181b9a39aea 100644 --- a/packages/CarSystemUI/res/values/colors.xml +++ b/packages/CarSystemUI/res/values/colors.xml @@ -33,7 +33,7 @@ <!-- colors for status bar --> <color name="system_bar_background_pill_color">#282A2D</color> - <color name="system_bar_user_icon_color">#FFFFFF</color> + <color name="system_bar_icon_color">#FFFFFF</color> <color name="system_bar_text_color">#FFFFFF</color> <color name="status_bar_background_color">#33000000</color> <drawable name="system_bar_background">@color/status_bar_background_color</drawable> diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml index fe060ac8e707..86bfa751d085 100644 --- a/packages/CarSystemUI/res/values/dimens.xml +++ b/packages/CarSystemUI/res/values/dimens.xml @@ -46,6 +46,10 @@ in frameworks/base/core package and thus will have no effect if set here. See car_product overlay for car specific defaults--> + <!-- Overrides the space between each status icon in the system bar --> + <dimen name="status_bar_system_icon_spacing">16dp</dimen> + <!-- Overrides the size of the network signal icon --> + <dimen name="signal_icon_size">32dp</dimen> <dimen name="system_bar_user_icon_padding">16dp</dimen> <dimen name="system_bar_user_icon_drawing_size">36dp</dimen> <!-- Padding on either side of the group of all system bar buttons --> diff --git a/packages/CarSystemUI/res/values/styles.xml b/packages/CarSystemUI/res/values/styles.xml index f242db0aec09..f5de2fde7b1a 100644 --- a/packages/CarSystemUI/res/values/styles.xml +++ b/packages/CarSystemUI/res/values/styles.xml @@ -49,5 +49,6 @@ <item name="android:padding">@dimen/system_bar_button_padding</item> <item name="android:gravity">center</item> <item name="android:background">?android:attr/selectableItemBackground</item> + <item name="unselectedAlpha">0.56</item> </style> </resources>
\ No newline at end of file diff --git a/packages/CarSystemUI/samples/sample2/rro/Android.bp b/packages/CarSystemUI/samples/sample2/rro/Android.bp new file mode 100644 index 000000000000..bf68e414ca1c --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/Android.bp @@ -0,0 +1,27 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_app { + name: "CarSystemUISampleTwoRRO", + resource_dirs: ["res"], + certificate: "platform", + platform_apis: true, + manifest: "AndroidManifest.xml", + aaptflags: [ + "--no-resource-deduping", + "--no-resource-removal", + ] +}
\ No newline at end of file diff --git a/packages/CarSystemUI/samples/sample2/rro/AndroidManifest.xml b/packages/CarSystemUI/samples/sample2/rro/AndroidManifest.xml new file mode 100644 index 000000000000..5c25056f7915 --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/AndroidManifest.xml @@ -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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.rro"> + <overlay + android:targetPackage="com.android.systemui" + android:isStatic="false" + android:resourcesMap="@xml/car_sysui_overlays" + /> +</manifest>
\ No newline at end of file diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_apps.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_apps.xml new file mode 100644 index 000000000000..a8d8a2f241f6 --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_apps.xml @@ -0,0 +1,25 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:viewportWidth="44" + android:viewportHeight="44" + android:width="44dp" + android:height="44dp"> +<path + android:pathData="M7.33333333 14.6666667L14.6666667 14.6666667L14.6666667 7.33333333L7.33333333 7.33333333L7.33333333 14.6666667ZM18.3333333 36.6666667L25.6666667 36.6666667L25.6666667 29.3333333L18.3333333 29.3333333L18.3333333 36.6666667ZM7.33333333 36.6666667L14.6666667 36.6666667L14.6666667 29.3333333L7.33333333 29.3333333L7.33333333 36.6666667ZM7.33333333 25.6666667L14.6666667 25.6666667L14.6666667 18.3333333L7.33333333 18.3333333L7.33333333 25.6666667ZM18.3333333 25.6666667L25.6666667 25.6666667L25.6666667 18.3333333L18.3333333 18.3333333L18.3333333 25.6666667ZM29.3333333 7.33333333L29.3333333 14.6666667L36.6666667 14.6666667L36.6666667 7.33333333L29.3333333 7.33333333ZM18.3333333 14.6666667L25.6666667 14.6666667L25.6666667 7.33333333L18.3333333 7.33333333L18.3333333 14.6666667ZM29.3333333 25.6666667L36.6666667 25.6666667L36.6666667 18.3333333L29.3333333 18.3333333L29.3333333 25.6666667ZM29.3333333 36.6666667L36.6666667 36.6666667L36.6666667 29.3333333L29.3333333 29.3333333L29.3333333 36.6666667Z" + android:fillColor="@color/car_nav_icon_fill_color" /> +</vector>
\ No newline at end of file diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_music.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_music.xml new file mode 100644 index 000000000000..6339ebb3ea8d --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_music.xml @@ -0,0 +1,25 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:viewportWidth="44" + android:viewportHeight="44" + android:width="44dp" + android:height="44dp"> + <path + android:pathData="M22 5.5L22 24.8416667C20.9183333 24.2183333 19.6716667 23.8333333 18.3333333 23.8333333C14.2816667 23.8333333 11 27.115 11 31.1666667C11 35.2183333 14.2816667 38.5 18.3333333 38.5C22.385 38.5 25.6666667 35.2183333 25.6666667 31.1666667L25.6666667 12.8333333L33 12.8333333L33 5.5L22 5.5Z" + android:fillColor="@color/car_nav_icon_fill_color" /> +</vector>
\ No newline at end of file diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_navigation.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_navigation.xml new file mode 100644 index 000000000000..e1fabe07cdeb --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_navigation.xml @@ -0,0 +1,25 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:viewportWidth="44" + android:viewportHeight="44" + android:width="44dp" + android:height="44dp"> + <path + android:pathData="M39.8016667 20.6983333L23.3016667 4.19833333C22.5866667 3.48333333 21.4316667 3.48333333 20.7166667 4.19833333L4.21666667 20.6983333C3.50166667 21.4133333 3.50166667 22.5683333 4.21666667 23.2833333L20.7166667 39.7833333C21.4316667 40.4983333 22.5866667 40.4983333 23.3016667 39.7833333L39.8016667 23.2833333C40.5166667 22.5866667 40.5166667 21.4316667 39.8016667 20.6983333ZM25.6666667 26.5833333L25.6666667 22L18.3333333 22L18.3333333 27.5L14.6666667 27.5L14.6666667 20.1666667C14.6666667 19.1583333 15.4916667 18.3333333 16.5 18.3333333L25.6666667 18.3333333L25.6666667 13.75L32.0833333 20.1666667L25.6666667 26.5833333Z" + android:fillColor="@color/car_nav_icon_fill_color" /> +</vector>
\ No newline at end of file diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_notification.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_notification.xml new file mode 100644 index 000000000000..3c3fefc9782a --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_notification.xml @@ -0,0 +1,25 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:viewportWidth="44" + android:viewportHeight="44"> + <path + android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z" + android:fillColor="@color/car_nav_icon_fill_color" /> +</vector>
\ No newline at end of file diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_overview.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_overview.xml new file mode 100644 index 000000000000..f185eb9afb75 --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_overview.xml @@ -0,0 +1,26 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:viewportWidth="44" + android:viewportHeight="44" + android:width="44dp" + android:height="44dp"> + <path + android:pathData="M36.92857 22.39286A14.53571 14.53571 0 0 1 7.857143 22.39286A14.53571 14.53571 0 0 1 36.92857 22.39286Z" + android:strokeColor="@color/car_nav_icon_fill_color" + android:strokeWidth="4" /> +</vector>
\ No newline at end of file diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_phone.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_phone.xml new file mode 100644 index 000000000000..50e36b5a6e3c --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_phone.xml @@ -0,0 +1,25 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:viewportWidth="44" + android:viewportHeight="44" + android:width="44dp" + android:height="44dp"> + <path + android:pathData="M12.1366667 19.7816667C14.7766667 24.97 19.03 29.205 24.2183333 31.8633333L28.2516667 27.83C28.7466667 27.335 29.48 27.17 30.1216667 27.39C32.175 28.0683333 34.3933333 28.435 36.6666667 28.435C37.675 28.435 38.5 29.26 38.5 30.2683333L38.5 36.6666667C38.5 37.675 37.675 38.5 36.6666667 38.5C19.4516667 38.5 5.5 24.5483333 5.5 7.33333333C5.5 6.325 6.325 5.5 7.33333333 5.5L13.75 5.5C14.7583333 5.5 15.5833333 6.325 15.5833333 7.33333333C15.5833333 9.625 15.95 11.825 16.6283333 13.8783333C16.83 14.52 16.6833333 15.235 16.17 15.7483333L12.1366667 19.7816667Z" + android:fillColor="@color/car_nav_icon_fill_color" /> +</vector>
\ No newline at end of file diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/system_bar_background.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/system_bar_background.xml new file mode 100644 index 000000000000..6161ad9b041c --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/system_bar_background.xml @@ -0,0 +1,33 @@ +<?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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > + <corners + android:topLeftRadius="0dp" + android:topRightRadius="10dp" + android:bottomLeftRadius="0dp" + android:bottomRightRadius="0dp" + /> + <solid + android:color="#404040" + /> + <padding + android:left="0dp" + android:top="0dp" + android:right="0dp" + android:bottom="0dp" + /> +</shape>
\ No newline at end of file diff --git a/packages/CarSystemUI/samples/sample2/rro/res/layout/car_left_navigation_bar.xml b/packages/CarSystemUI/samples/sample2/rro/res/layout/car_left_navigation_bar.xml new file mode 100644 index 000000000000..bd6065c3de9a --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/layout/car_left_navigation_bar.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 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. +*/ +--> + +<com.android.systemui.car.navigationbar.CarNavigationBarView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:systemui="http://schemas.android.com/apk/res-auto" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:orientation="vertical" + android:background="@drawable/system_bar_background"> + + <com.android.systemui.car.navigationbar.CarNavigationButton + android:id="@+id/home" + style="@style/NavigationBarButton" + systemui:componentNames="com.android.car.carlauncher/.CarLauncher" + systemui:icon="@drawable/car_ic_overview" + systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end" + systemui:highlightWhenSelected="true" + /> + + <com.android.systemui.car.navigationbar.CarNavigationButton + android:id="@+id/maps_nav" + style="@style/NavigationBarButton" + systemui:categories="android.intent.category.APP_MAPS" + systemui:icon="@drawable/car_ic_navigation" + systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;launchFlags=0x14000000;end" + systemui:highlightWhenSelected="true" + /> + + <com.android.systemui.car.navigationbar.CarNavigationButton + android:id="@+id/music_nav" + style="@style/NavigationBarButton" + systemui:categories="android.intent.category.APP_MUSIC" + systemui:icon="@drawable/car_ic_music" + systemui:intent="intent:#Intent;action=android.car.intent.action.MEDIA_TEMPLATE;launchFlags=0x10000000;end" + systemui:packages="com.android.car.media" + systemui:highlightWhenSelected="true" + /> + + + <com.android.systemui.car.navigationbar.CarNavigationButton + android:id="@+id/grid_nav" + style="@style/NavigationBarButton" + systemui:componentNames="com.android.car.carlauncher/.AppGridActivity" + systemui:icon="@drawable/car_ic_apps" + systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end" + systemui:highlightWhenSelected="true" + /> + + <com.android.systemui.car.navigationbar.CarNavigationButton + android:id="@+id/phone_nav" + style="@style/NavigationBarButton" + systemui:icon="@drawable/car_ic_phone" + systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end" + systemui:packages="com.android.car.dialer" + systemui:highlightWhenSelected="true" + /> + + <com.android.systemui.car.navigationbar.CarNavigationButton + android:id="@+id/notifications" + style="@style/NavigationBarButton" + systemui:highlightWhenSelected="true" + systemui:icon="@drawable/car_ic_notification" + systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="bottom" + android:orientation="vertical"> + + <com.android.systemui.statusbar.policy.Clock + android:id="@+id/clock" + android:textAppearance="@style/TextAppearance.StatusBar.Clock" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:singleLine="true" + android:gravity="center_horizontal" + android:paddingBottom="20dp" + /> + + <Space + android:layout_height="10dp" + android:layout_width="match_parent"/> + + </LinearLayout> + +</com.android.systemui.car.navigationbar.CarNavigationBarView> diff --git a/packages/CarSystemUI/samples/sample2/rro/res/values/attrs.xml b/packages/CarSystemUI/samples/sample2/rro/res/values/attrs.xml new file mode 100644 index 000000000000..7ba333468422 --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/values/attrs.xml @@ -0,0 +1,28 @@ +<?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. + --> + +<resources> + <attr name="broadcast" format="boolean"/> + <attr name="icon" format="reference"/> + <attr name="selectedIcon" format="reference"/> + <attr name="intent" format="string"/> + <attr name="longIntent" format="string"/> + <attr name="componentNames" format="string" /> + <attr name="highlightWhenSelected" format="boolean" /> + <attr name="categories" format="string"/> + <attr name="packages" format="string" /> +</resources> diff --git a/packages/CarSystemUI/samples/sample2/rro/res/values/colors.xml b/packages/CarSystemUI/samples/sample2/rro/res/values/colors.xml new file mode 100644 index 000000000000..c32d638681a2 --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/values/colors.xml @@ -0,0 +1,20 @@ +<?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. + --> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <color name="car_nav_icon_fill_color">#8F8F8F</color> + <color name="car_nav_icon_fill_color_selected">#FFFFFF</color> +</resources> diff --git a/packages/CarSystemUI/samples/sample2/rro/res/values/config.xml b/packages/CarSystemUI/samples/sample2/rro/res/values/config.xml new file mode 100644 index 000000000000..89c7bd4df2bc --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/values/config.xml @@ -0,0 +1,57 @@ +<?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. + --> + +<resources> + <!-- Configure which system bars should be displayed. --> + <bool name="config_enableTopNavigationBar">false</bool> + <bool name="config_enableLeftNavigationBar">true</bool> + <bool name="config_enableRightNavigationBar">false</bool> + <bool name="config_enableBottomNavigationBar">false</bool> + + <!-- Configure the type of each system bar. Each system bar must have a unique type. --> + <!-- STATUS_BAR = 0--> + <!-- NAVIGATION_BAR = 1--> + <!-- STATUS_BAR_EXTRA = 2--> + <!-- NAVIGATION_BAR_EXTRA = 3--> + <integer name="config_topSystemBarType">0</integer> + <integer name="config_leftSystemBarType">1</integer> + <integer name="config_rightSystemBarType">2</integer> + <integer name="config_bottomSystemBarType">3</integer> + + <!-- Configure the relative z-order among the system bars. When two system bars overlap (e.g. + if both top bar and left bar are enabled, it creates an overlapping space in the upper left + corner), the system bar with the higher z-order takes the overlapping space and padding is + applied to the other bar.--> + <!-- NOTE: If two overlapping system bars have the same z-order, SystemBarConfigs will throw a + RuntimeException, since their placing order cannot be determined. Bars that do not overlap + are allowed to have the same z-order. --> + <!-- NOTE: If the z-order of a bar is 10 or above, it will also appear on top of HUN's. --> + <integer name="config_topSystemBarZOrder">0</integer> + <integer name="config_leftSystemBarZOrder">10</integer> + <integer name="config_rightSystemBarZOrder">10</integer> + <integer name="config_bottomSystemBarZOrder">10</integer> + + <!-- Whether heads-up notifications should be shown on the bottom. If false, heads-up + notifications will be shown pushed to the top of their parent container. If true, they will + be shown pushed to the bottom of their parent container. If true, then should override + config_headsUpNotificationAnimationHelper to use a different AnimationHelper, such as + com.android.car.notification.headsup.animationhelper. + CarHeadsUpNotificationBottomAnimationHelper. --> + <bool name="config_showHeadsUpNotificationOnBottom">true</bool> + + <string name="config_notificationPanelViewMediator" translatable="false">com.android.systemui.car.notification.NotificationPanelViewMediator</string> +</resources>
\ No newline at end of file diff --git a/packages/CarSystemUI/samples/sample2/rro/res/values/styles.xml b/packages/CarSystemUI/samples/sample2/rro/res/values/styles.xml new file mode 100644 index 000000000000..136dc3b6df18 --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/values/styles.xml @@ -0,0 +1,35 @@ +<?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. + --> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <style name="TextAppearance.StatusBar.Clock" + parent="@*android:style/TextAppearance.StatusBar.Icon"> + <item name="android:textSize">40sp</item> + <item name="android:fontFamily">sans-serif-regular</item> + <item name="android:textColor">#FFFFFF</item> + </style> + + <style name="NavigationBarButton"> + <item name="android:layout_height">96dp</item> + <item name="android:layout_width">96dp</item> + <item name="android:background">?android:attr/selectableItemBackground</item> + </style> + + <style name="TextAppearance.CarStatus" parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textSize">30sp</item> + <item name="android:textColor">#FFFFFF</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/CarSystemUI/samples/sample2/rro/res/xml/car_sysui_overlays.xml b/packages/CarSystemUI/samples/sample2/rro/res/xml/car_sysui_overlays.xml new file mode 100644 index 000000000000..58a535b4e000 --- /dev/null +++ b/packages/CarSystemUI/samples/sample2/rro/res/xml/car_sysui_overlays.xml @@ -0,0 +1,66 @@ + +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<overlay> + <item target="layout/car_left_navigation_bar" value="@layout/car_left_navigation_bar"/> + + <item target="attr/icon" value="@attr/icon"/> + <item target="attr/selectedIcon" value="@attr/selectedIcon"/> + <item target="attr/intent" value="@attr/intent"/> + <item target="attr/longIntent" value="@attr/longIntent"/> + <item target="attr/componentNames" value="@attr/componentNames"/> + <item target="attr/highlightWhenSelected" value="@attr/highlightWhenSelected"/> + <item target="attr/categories" value="@attr/categories"/> + <item target="attr/packages" value="@attr/packages"/> + <!-- start the intent as a broad cast instead of an activity if true--> + <item target="attr/broadcast" value="@attr/broadcast"/> + + <item target="drawable/car_ic_overview" value="@drawable/car_ic_overview" /> + <item target="drawable/car_ic_apps" value="@drawable/car_ic_apps" /> + <item target="drawable/car_ic_music" value="@drawable/car_ic_music" /> + <item target="drawable/car_ic_phone" value="@drawable/car_ic_phone" /> + <item target="drawable/car_ic_navigation" value="@drawable/car_ic_navigation" /> + + <item target="style/NavigationBarButton" value="@style/NavigationBarButton"/> + + <item target="color/car_nav_icon_fill_color" value="@color/car_nav_icon_fill_color" /> + + <item target="bool/config_enableTopNavigationBar" value="@bool/config_enableTopNavigationBar"/> + <item target="bool/config_enableLeftNavigationBar" value="@bool/config_enableLeftNavigationBar"/> + <item target="bool/config_enableRightNavigationBar" value="@bool/config_enableRightNavigationBar"/> + <item target="bool/config_enableBottomNavigationBar" value="@bool/config_enableBottomNavigationBar"/> + <item target="bool/config_showHeadsUpNotificationOnBottom" value="@bool/config_showHeadsUpNotificationOnBottom"/> + + <item target="integer/config_topSystemBarType" value="@integer/config_topSystemBarType"/> + <item target="integer/config_leftSystemBarType" value="@integer/config_leftSystemBarType"/> + <item target="integer/config_rightSystemBarType" value="@integer/config_rightSystemBarType"/> + <item target="integer/config_bottomSystemBarType" value="@integer/config_bottomSystemBarType"/> + + <item target="integer/config_topSystemBarZOrder" value="@integer/config_topSystemBarZOrder"/> + <item target="integer/config_leftSystemBarZOrder" value="@integer/config_leftSystemBarZOrder"/> + <item target="integer/config_rightSystemBarZOrder" value="@integer/config_rightSystemBarZOrder"/> + <item target="integer/config_bottomSystemBarZOrder" value="@integer/config_bottomSystemBarZOrder"/> + + <item target="string/config_notificationPanelViewMediator" value="@string/config_notificationPanelViewMediator"/> + + <item target="id/home" value="@id/home"/> + <item target="id/maps_nav" value="@id/maps_nav"/> + <item target="id/music_nav" value="@id/music_nav"/> + <item target="id/grid_nav" value="@id/grid_nav"/> + <item target="id/phone_nav" value="@id/phone_nav"/> + <item target="id/notifications" value="@id/notifications"/> +</overlay>
\ No newline at end of file diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java index b113d29f00e6..f2ca4956be07 100644 --- a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java +++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java @@ -50,6 +50,7 @@ public class DisplaySystemBarsController extends DisplayImeController { private final Context mContext; private final DisplayController mDisplayController; + private final Handler mHandler; private SparseArray<PerDisplay> mPerDisplaySparseArray; public DisplaySystemBarsController( @@ -58,9 +59,10 @@ public class DisplaySystemBarsController extends DisplayImeController { DisplayController displayController, @Main Handler mainHandler, TransactionPool transactionPool) { - super(wmService, displayController, mainHandler, transactionPool); + super(wmService, displayController, (r) -> mainHandler.post(r), transactionPool); mContext = context; mDisplayController = displayController; + mHandler = mainHandler; } @Override 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 e05bd3c22ae8..d3aa977f85b1 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 @@ -21,8 +21,6 @@ import static android.location.LocationManager.NETWORK_PROVIDER; import static androidx.test.ext.truth.location.LocationSubject.assertThat; -import static com.google.common.truth.Truth.assertThat; - import android.content.Context; import android.location.Criteria; import android.location.Location; diff --git a/packages/InputDevices/res/values-ar/strings.xml b/packages/InputDevices/res/values-ar/strings.xml index 3b00576afd89..bf508b2ad7d1 100644 --- a/packages/InputDevices/res/values-ar/strings.xml +++ b/packages/InputDevices/res/values-ar/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"الألمانية السويسرية"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"البلجيكية"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"البلغارية"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"بلغارية صوتية"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"الإيطالية"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"الدانماركية"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"النرويجية"</string> diff --git a/packages/InputDevices/res/values-bn/strings.xml b/packages/InputDevices/res/values-bn/strings.xml index 19292780875e..f387414f50c8 100644 --- a/packages/InputDevices/res/values-bn/strings.xml +++ b/packages/InputDevices/res/values-bn/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"সুইস জার্মান"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"বেলজিয়ান"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"বুলগেরীয়"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"বুলগেরিয়ান, ফনেটিক"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"ইতালীয়"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"ডেনিশ"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"নরওয়েজীয়"</string> diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml index 99aed5e6ea0e..6b4f7ebf7a4a 100644 --- a/packages/InputDevices/res/values-cs/strings.xml +++ b/packages/InputDevices/res/values-cs/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"švýcarské (němčina)"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgické"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bulharské"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulharská fonetická klávesnice"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"italské"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"dánské"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norské"</string> diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml index 697fff6f1339..45aca35eb012 100644 --- a/packages/InputDevices/res/values-fr-rCA/strings.xml +++ b/packages/InputDevices/res/values-fr-rCA/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Allemand (Suisse)"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belge"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgare"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Phonétique bulgare"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italien"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danois"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvégien"</string> diff --git a/packages/InputDevices/res/values-fr/strings.xml b/packages/InputDevices/res/values-fr/strings.xml index 2e14019340fc..b55a3c99f410 100644 --- a/packages/InputDevices/res/values-fr/strings.xml +++ b/packages/InputDevices/res/values-fr/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Allemand (Suisse)"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belge"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgare"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Phonétique bulgare"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italien"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danois"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvégien"</string> diff --git a/packages/InputDevices/res/values-hi/strings.xml b/packages/InputDevices/res/values-hi/strings.xml index 2ffebdd2c1ff..7d1e2f8106e7 100644 --- a/packages/InputDevices/res/values-hi/strings.xml +++ b/packages/InputDevices/res/values-hi/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"स्विस जर्मन"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"बेल्जियाई"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"बुल्गारियाई"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"बुल्गेरियन, फ़ोनेटिक"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"इटैलियन"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"डैनिश"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"नार्वेजियाई"</string> diff --git a/packages/InputDevices/res/values-kk/strings.xml b/packages/InputDevices/res/values-kk/strings.xml index cfdc3f85de4a..dfe8c56802c7 100644 --- a/packages/InputDevices/res/values-kk/strings.xml +++ b/packages/InputDevices/res/values-kk/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Швейцариялық неміс"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Бельгиялық"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Болгар"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Болгар (фонетикалық)"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"Италиян"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"Дат"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвег"</string> diff --git a/packages/InputDevices/res/values-km/strings.xml b/packages/InputDevices/res/values-km/strings.xml index 2aaf816c7deb..3bd7f202f287 100644 --- a/packages/InputDevices/res/values-km/strings.xml +++ b/packages/InputDevices/res/values-km/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"អាល្លឺម៉ង់ ស្វីស"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"បែលហ្ស៊ិក"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ប៊ុលហ្ការី"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ប៊ុលហ្គារី សូរសព្ទ"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"អ៊ីតាលី"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"ដាណឺម៉ាក"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ន័រវែស"</string> diff --git a/packages/InputDevices/res/values-mk/strings.xml b/packages/InputDevices/res/values-mk/strings.xml index c036409c9c62..9584e156ad9c 100644 --- a/packages/InputDevices/res/values-mk/strings.xml +++ b/packages/InputDevices/res/values-mk/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Германски (Швајцарија)"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Белгиски"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Бугарски"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Бугарска, фонетска"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"Италијански"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"Дански"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвешки"</string> diff --git a/packages/InputDevices/res/values-ne/strings.xml b/packages/InputDevices/res/values-ne/strings.xml index 24816c1126e3..4801f752fa6c 100644 --- a/packages/InputDevices/res/values-ne/strings.xml +++ b/packages/InputDevices/res/values-ne/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"स्विस-जर्मन"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"बेल्जियन"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"बुल्गेरियन"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"बुल्गेरियन फोनेटिक किबोर्ड"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"इटालियन"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"डेनिश"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"नर्वेजियन"</string> diff --git a/packages/InputDevices/res/values-ru/strings.xml b/packages/InputDevices/res/values-ru/strings.xml index 0cb4f34d4ab1..70668133ae8b 100644 --- a/packages/InputDevices/res/values-ru/strings.xml +++ b/packages/InputDevices/res/values-ru/strings.xml @@ -27,7 +27,7 @@ <string name="keyboard_layout_finnish" msgid="5585659438924315466">"финский"</string> <string name="keyboard_layout_croatian" msgid="4172229471079281138">"хорватский"</string> <string name="keyboard_layout_czech" msgid="1349256901452975343">"чешский"</string> - <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Чешский (QWERTY)"</string> + <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"чешский (QWERTY)"</string> <string name="keyboard_layout_estonian" msgid="8775830985185665274">"эстонский"</string> <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"венгерский"</string> <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"исландский"</string> @@ -44,9 +44,9 @@ <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"испанский (Латинская Америка)"</string> <string name="keyboard_layout_latvian" msgid="4405417142306250595">"латышский"</string> <string name="keyboard_layout_persian" msgid="3920643161015888527">"Персидский"</string> - <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"Азербайджанский"</string> + <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"азербайджанский"</string> <string name="keyboard_layout_polish" msgid="1121588624094925325">"польский"</string> - <string name="keyboard_layout_belarusian" msgid="7619281752698687588">"Белорусский"</string> - <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Монгольский"</string> - <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Грузинский"</string> + <string name="keyboard_layout_belarusian" msgid="7619281752698687588">"белорусский"</string> + <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"монгольский"</string> + <string name="keyboard_layout_georgian" msgid="4596185456863747454">"грузинский"</string> </resources> diff --git a/packages/InputDevices/res/values-sq/strings.xml b/packages/InputDevices/res/values-sq/strings.xml index b3677cd699b4..9b4fe9ee7580 100644 --- a/packages/InputDevices/res/values-sq/strings.xml +++ b/packages/InputDevices/res/values-sq/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"gjermanishte zvicerane"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"belge"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bullgarisht"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Tastierë bullgare, fonetike"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"italisht"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"danisht"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norvegjisht"</string> diff --git a/packages/InputDevices/res/values-ta/strings.xml b/packages/InputDevices/res/values-ta/strings.xml index b75b57d5f358..d3c6000fd9d4 100644 --- a/packages/InputDevices/res/values-ta/strings.xml +++ b/packages/InputDevices/res/values-ta/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ஸ்விஸ் ஜெர்மன்"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"பெல்ஜியன்"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"பல்கேரியன்"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"பல்கேரியன், ஒலிப்புமுறை"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"இத்தாலியன்"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"டேனிஷ்"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"நார்வேஜியன்"</string> diff --git a/packages/InputDevices/res/values-tl/strings.xml b/packages/InputDevices/res/values-tl/strings.xml index 21ea5de903bd..b9aee76d400c 100644 --- a/packages/InputDevices/res/values-tl/strings.xml +++ b/packages/InputDevices/res/values-tl/strings.xml @@ -19,8 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Swiss German"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgian"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarian"</string> - <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) --> - <skip /> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarian, Phonetic"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italian"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danish"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegian"</string> diff --git a/packages/InputDevices/res/values-vi/strings.xml b/packages/InputDevices/res/values-vi/strings.xml index 1b42ece3deed..76fd0bff0e2a 100644 --- a/packages/InputDevices/res/values-vi/strings.xml +++ b/packages/InputDevices/res/values-vi/strings.xml @@ -19,7 +19,7 @@ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Tiếng Đức Thụy Sĩ"</string> <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Tiếng Bỉ"</string> <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Tiếng Bungary"</string> - <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Tiếng Bulgaria, Ngữ âm"</string> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Tiếng Bulgaria, Phiên âm"</string> <string name="keyboard_layout_italian" msgid="6497079660449781213">"Tiếng Ý"</string> <string name="keyboard_layout_danish" msgid="8036432066627127851">"Tiếng Đan Mạch"</string> <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Tiếng Na Uy"</string> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index 696ed2955daa..f13d9b860dce 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -538,7 +538,7 @@ <string name="user_setup_button_setup_now" msgid="1708269547187760639">"지금 설정"</string> <string name="user_setup_button_setup_later" msgid="8712980133555493516">"나중에"</string> <string name="user_add_user_type_title" msgid="551279664052914497">"추가"</string> - <string name="user_new_user_name" msgid="60979820612818840">"새 사용자"</string> + <string name="user_new_user_name" msgid="60979820612818840">"신규 사용자"</string> <string name="user_new_profile_name" msgid="2405500423304678841">"새 프로필"</string> <string name="user_info_settings_title" msgid="6351390762733279907">"사용자 정보"</string> <string name="profile_info_settings_title" msgid="105699672534365099">"프로필 정보"</string> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index bc6660184fe3..d2485cc49b6e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -764,6 +764,9 @@ class SettingsProtoDumpUtil { Settings.Global.GLOBAL_SETTINGS_ANGLE_ALLOWLIST, GlobalSettingsProto.Gpu.ANGLE_ALLOWLIST); dumpSetting(s, p, + Settings.Global.ANGLE_EGL_FEATURES, + GlobalSettingsProto.Gpu.ANGLE_EGL_FEATURES); + dumpSetting(s, p, Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX, GlobalSettingsProto.Gpu.SHOW_ANGLE_IN_USE_DIALOG); dumpSetting(s, p, diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 9788b30f82c4..e027fd397cdd 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -437,6 +437,7 @@ public class SettingsBackupTest { Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG, Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED, Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, + Settings.Global.SHOW_PEOPLE_SPACE, Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG, Settings.Global.SHOW_TEMPERATURE_WARNING, Settings.Global.SHOW_USB_TEMPERATURE_ALARM, @@ -502,6 +503,7 @@ public class SettingsBackupTest { Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, Settings.Global.GLOBAL_SETTINGS_ANGLE_ALLOWLIST, + Settings.Global.ANGLE_EGL_FEATURES, Settings.Global.UPDATABLE_DRIVER_ALL_APPS, Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS, Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index f5f58efb72e6..a9279971ce1e 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -326,6 +326,9 @@ <!-- Permission needed for CTS test - DisplayTest --> <uses-permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS" /> + <!-- Permission needed for CTS test - TimeManagerTest --> + <uses-permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index ce427cbe776b..0eac4add7b09 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -511,14 +511,14 @@ public class BugreportProgressService extends Service { } if (msg.what != MSG_SERVICE_COMMAND) { - // Sanity check. + // Confidence check. Log.e(TAG, "Invalid message type: " + msg.what); return; } // At this point it's handling onStartCommand(), with the intent passed as an Extra. if (!(msg.obj instanceof Intent)) { - // Sanity check. + // Confidence check. Log.wtf(TAG, "handleMessage(): invalid msg.obj type: " + msg.obj); return; } diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java index cd3cad1d6351..3b02e3b46557 100644 --- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java +++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java @@ -466,7 +466,7 @@ public class BugreportReceiverTest { // Clear properties mContext.getSharedPreferences(PREFS_BUGREPORT, Context.MODE_PRIVATE) .edit().clear().commit(); - // Sanity check... + // Confidence check... assertEquals("Did not reset properties", STATE_UNKNOWN, getWarningState(mContext, STATE_UNKNOWN)); } else { diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index cf78a131d6fb..505ef7a58843 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -518,7 +518,7 @@ <!-- started from PipController --> <activity - android:name=".pip.tv.PipMenuActivity" + android:name="com.android.wm.shell.pip.tv.PipMenuActivity" android:permission="com.android.systemui.permission.SELF" android:exported="false" android:theme="@style/PipTheme" diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml index 92d0858a1a31..ac8b7b5812bd 100644 --- a/packages/SystemUI/res/layout/media_output_list_item.xml +++ b/packages/SystemUI/res/layout/media_output_list_item.xml @@ -75,18 +75,6 @@ android:textSize="12sp" android:fontFamily="roboto-regular" android:visibility="gone"/> - <ProgressBar - android:id="@+id/volume_indeterminate_progress" - style="@*android:style/Widget.Material.ProgressBar.Horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginEnd="15dp" - android:layout_marginBottom="1dp" - android:layout_alignParentBottom="true" - android:indeterminate="true" - android:indeterminateOnly="true" - android:visibility="gone"/> <SeekBar android:id="@+id/volume_seekbar" android:layout_width="match_parent" @@ -94,6 +82,17 @@ android:layout_alignParentBottom="true"/> </RelativeLayout> + <ProgressBar + android:id="@+id/volume_indeterminate_progress" + style="@*android:style/Widget.Material.ProgressBar.Horizontal" + android:layout_width="258dp" + android:layout_height="18dp" + android:layout_marginStart="68dp" + android:layout_marginTop="40dp" + android:indeterminate="true" + android:indeterminateOnly="true" + android:visibility="gone"/> + <View android:layout_width="1dp" android:layout_height="36dp" diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index e1d195028dcb..b09e5b9b30a3 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -899,9 +899,9 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"إظهار رموز الإشعارات ذات الأولوية المنخفضة"</string> <string name="other" msgid="429768510980739978">"غير ذلك"</string> - <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"إزالة البطاقة"</string> - <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"إضافة بطاقة إلى النهاية"</string> - <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"نقل البطاقة"</string> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"إزالة بطاقة"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"إضافة بطاقة إلى نهاية الإعدادات السريعة"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"نقل بطاقة"</string> <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"إضافة بطاقة"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"الانتقال إلى <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"الإضافة إلى الموضع <xliff:g id="POSITION">%1$d</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index fcdd286b6f55..aed9522ced21 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -879,20 +879,13 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"কম-গুরুত্বপূর্ণ বিজ্ঞপ্তির আইকন দেখুন"</string> <string name="other" msgid="429768510980739978">"অন্যান্য"</string> - <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) --> - <skip /> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"টাইল সরান"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"শেষে টাইল যোগ করুন"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"টাইল সরান"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"টাইল যোগ করুন"</string> + <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g>-এ সরান"</string> + <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"অবস্থান <xliff:g id="POSITION">%1$d</xliff:g>-এ যোগ করুন"</string> + <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"অবস্থান <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"দ্রুত সেটিংস সম্পাদক৷"</string> <string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> বিজ্ঞপ্তি: <xliff:g id="ID_2">%2$s</xliff:g>"</string> <string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"সেটিংস খুলুন।"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 783f7877e230..0a0368042d5f 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -880,9 +880,9 @@ <string name="tuner_low_priority" msgid="8412666814123009820">"Mostra les icones de notificació amb prioritat baixa"</string> <string name="other" msgid="429768510980739978">"Altres"</string> <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"suprimir el mosaic"</string> - <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"afegir una targeta al final"</string> - <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mou la targeta"</string> - <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Afegeix una targeta"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"afegir un mosaic al final"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mou el mosaic"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Afegeix un mosaic"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mou a la posició <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Afegeix a la posició <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posició <xliff:g id="POSITION">%1$d</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 3f3d6be63f9c..419e62510d50 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -889,20 +889,13 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"Zobrazit ikony oznámení s nízkou prioritou"</string> <string name="other" msgid="429768510980739978">"Jiné"</string> - <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) --> - <skip /> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"odstranit dlaždici"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"přidat dlaždici na konec"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Přesunout dlaždici"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Přidat dlaždici"</string> + <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Přesunout na pozici <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Přidat dlaždici na pozici <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozice <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor rychlého nastavení"</string> <string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Oznámení aplikace <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string> <string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Otevřít nastavení."</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 887aad5d9aa6..4dbd74ae4f48 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -28,15 +28,15 @@ <string name="battery_low_percent_format" msgid="4276661262843170964">"<xliff:g id="PERCENTAGE">%s</xliff:g> verbleibend"</string> <string name="battery_low_percent_format_hybrid" msgid="3985614339605686167">"Noch <xliff:g id="PERCENTAGE">%1$s</xliff:g> übrig; bei deinem Nutzungsmuster hast du noch ca. <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_low_percent_format_hybrid_short" msgid="5917433188456218857">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> ausstehend; noch ca. <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="battery_low_percent_format_saver_started" msgid="4968468824040940688">"Noch <xliff:g id="PERCENTAGE">%s</xliff:g>. Der Energiesparmodus ist aktiviert."</string> + <string name="battery_low_percent_format_saver_started" msgid="4968468824040940688">"Noch <xliff:g id="PERCENTAGE">%s</xliff:g>. Der Stromsparmodus ist aktiviert."</string> <string name="invalid_charger" msgid="4370074072117767416">"Aufladen über USB nicht möglich. Verwende das mit dem Gerät gelieferte Ladegerät."</string> <string name="invalid_charger_title" msgid="938685362320735167">"Aufladen über USB nicht möglich"</string> <string name="invalid_charger_text" msgid="2339310107232691577">"Verwende das mit dem Gerät gelieferte Ladegerät"</string> <string name="battery_low_why" msgid="2056750982959359863">"Einstellungen"</string> - <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"Energiesparmodus aktivieren?"</string> - <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"Über den Energiesparmodus"</string> + <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"Stromsparmodus aktivieren?"</string> + <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"Über den Stromsparmodus"</string> <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivieren"</string> - <string name="battery_saver_start_action" msgid="4553256017945469937">"Energiesparmodus aktivieren"</string> + <string name="battery_saver_start_action" msgid="4553256017945469937">"Stromsparmodus aktivieren"</string> <string name="status_bar_settings_settings_button" msgid="534331565185171556">"Einstellungen"</string> <string name="status_bar_settings_wifi_button" msgid="7243072479837270946">"WLAN"</string> <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Bildschirm automatisch drehen"</string> @@ -419,7 +419,7 @@ <string name="quick_settings_night_secondary_label_on_at" msgid="3584738542293528235">"An um <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_secondary_label_until" msgid="1883981263191927372">"Bis <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"Dunkles Design"</string> - <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Energiesparmodus"</string> + <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Stromsparmodus"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"An bei Sonnenuntergang"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Bis Sonnenaufgang"</string> <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"An um <xliff:g id="TIME">%s</xliff:g>"</string> @@ -497,9 +497,9 @@ <string name="user_remove_user_title" msgid="9124124694835811874">"Nutzer entfernen?"</string> <string name="user_remove_user_message" msgid="6702834122128031833">"Alle Apps und Daten dieses Nutzers werden gelöscht."</string> <string name="user_remove_user_remove" msgid="8387386066949061256">"Entfernen"</string> - <string name="battery_saver_notification_title" msgid="8419266546034372562">"Energiesparmodus ist aktiviert"</string> + <string name="battery_saver_notification_title" msgid="8419266546034372562">"Stromsparmodus ist aktiviert"</string> <string name="battery_saver_notification_text" msgid="2617841636449016951">"Reduzierung der Leistung und Hintergrunddaten"</string> - <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Energiesparmodus deaktivieren"</string> + <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Stromsparmodus deaktivieren"</string> <string name="media_projection_dialog_text" msgid="1755705274910034772">"Die App \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise angezeigte Passwörter und Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string> <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Der Anbieter dieser App erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise Passwörter, Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Aufnahme oder Stream starten?"</string> @@ -773,8 +773,8 @@ <item quantity="one">%d Minute</item> </plurals> <string name="battery_panel_title" msgid="5931157246673665963">"Akkunutzung"</string> - <string name="battery_detail_charging_summary" msgid="8821202155297559706">"Der Energiesparmodus ist beim Aufladen nicht verfügbar."</string> - <string name="battery_detail_switch_title" msgid="6940976502957380405">"Energiesparmodus"</string> + <string name="battery_detail_charging_summary" msgid="8821202155297559706">"Der Stromsparmodus ist beim Aufladen nicht verfügbar."</string> + <string name="battery_detail_switch_title" msgid="6940976502957380405">"Stromsparmodus"</string> <string name="battery_detail_switch_summary" msgid="3668748557848025990">"Reduzierung der Leistung und Hintergrunddaten"</string> <string name="keyboard_key_button_template" msgid="8005673627272051429">"Taste <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="keyboard_key_home" msgid="3734400625170020657">"Pos1"</string> @@ -961,11 +961,11 @@ <string name="slice_permission_checkbox" msgid="4242888137592298523">"<xliff:g id="APP">%1$s</xliff:g> darf Teile aus jeder beliebigen App anzeigen"</string> <string name="slice_permission_allow" msgid="6340449521277951123">"Zulassen"</string> <string name="slice_permission_deny" msgid="6870256451658176895">"Ablehnen"</string> - <string name="auto_saver_title" msgid="6873691178754086596">"Tippen zum Planen des Energiesparmodus"</string> + <string name="auto_saver_title" msgid="6873691178754086596">"Tippen zum Planen des Stromsparmodus"</string> <string name="auto_saver_text" msgid="3214960308353838764">"Aktivieren, wenn der Akku wahrscheinlich nicht mehr lange hält"</string> <string name="no_auto_saver_action" msgid="7467924389609773835">"Nein danke"</string> - <string name="auto_saver_enabled_title" msgid="4294726198280286333">"Geplanter Energiesparmodus aktiviert"</string> - <string name="auto_saver_enabled_text" msgid="7889491183116752719">"Der Energiesparmodus wird bei einem Akkustand von <xliff:g id="PERCENTAGE">%d</xliff:g> %% automatisch aktiviert."</string> + <string name="auto_saver_enabled_title" msgid="4294726198280286333">"Geplanter Stromsparmodus aktiviert"</string> + <string name="auto_saver_enabled_text" msgid="7889491183116752719">"Der Stromsparmodus wird bei einem Akkustand von <xliff:g id="PERCENTAGE">%d</xliff:g> %% automatisch aktiviert."</string> <string name="open_saver_setting_action" msgid="2111461909782935190">"Einstellungen"</string> <string name="auto_saver_okay_action" msgid="7815925750741935386">"Ok"</string> <string name="heap_dump_tile_name" msgid="2464189856478823046">"Dump SysUI Heap"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index dfd42c3ea54f..8a8f1e737741 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -246,7 +246,7 @@ <string name="accessibility_remove_notification" msgid="1641455251495815527">"Eliminar notificación"</string> <string name="accessibility_gps_enabled" msgid="4061313248217660858">"GPS habilitado"</string> <string name="accessibility_gps_acquiring" msgid="896207402196024040">"Adquisición de GPS"</string> - <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter habilitado"</string> + <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletipo habilitado"</string> <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Timbre en vibración"</string> <string name="accessibility_ringer_silent" msgid="8994620163934249882">"Timbre en silencio"</string> <!-- no translation found for accessibility_casting (8708751252897282313) --> @@ -879,8 +879,8 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar íconos de notificaciones con prioridad baja"</string> <string name="other" msgid="429768510980739978">"Otros"</string> - <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"Quitar tarjeta"</string> - <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"Agregar tarjeta al final"</string> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"quitar tarjeta"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"agregar tarjeta al final"</string> <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover la tarjeta"</string> <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Agregar tarjeta"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover a <xliff:g id="POSITION">%1$d</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index c1ab20c9ebb6..e8d2f4763348 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -881,11 +881,11 @@ <string name="other" msgid="429768510980739978">"Beste bat"</string> <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"kendu lauza"</string> <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"gehitu lauza amaieran"</string> - <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Aldatu tokiz lauza"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mugitu lauza"</string> <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Gehitu lauza"</string> - <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Eraman <xliff:g id="POSITION">%1$d</xliff:g>garren kokapenera"</string> - <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Gehitu <xliff:g id="POSITION">%1$d</xliff:g>garren kokapenean"</string> - <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Kokapena: <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Eraman <xliff:g id="POSITION">%1$d</xliff:g>garren lekura"</string> + <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Gehitu <xliff:g id="POSITION">%1$d</xliff:g>garren lekuan"</string> + <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>garren lekua"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Ezarpen bizkorren editorea."</string> <string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> zerbitzuaren jakinarazpena: <xliff:g id="ID_2">%2$s</xliff:g>"</string> <string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Ireki ezarpenak."</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index ea6de66db50f..ff89ce2dabcd 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -879,20 +879,13 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"Afficher les icônes de notification de faible priorité"</string> <string name="other" msgid="429768510980739978">"Autre"</string> - <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) --> - <skip /> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"retirer la tuile"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ajouter la tuile à la fin"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Déplacer la tuile"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ajouter la tuile"</string> + <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Déplacer vers <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ajouter à la position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Éditeur de paramètres rapides."</string> <string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notification <xliff:g id="ID_1">%1$s</xliff:g> : <xliff:g id="ID_2">%2$s</xliff:g>"</string> <string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Ouvrir les paramètres."</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index fc2691e3570d..a126cfaadd65 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -879,20 +879,13 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"Afficher les icônes de notification à faible priorité"</string> <string name="other" msgid="429768510980739978">"Autre"</string> - <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) --> - <skip /> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"supprimer la carte"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ajouter la carte à la fin"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Déplacer la carte"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ajouter une carte"</string> + <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Déplacer vers <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ajouter à la position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Éditeur de configuration rapide."</string> <string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notification <xliff:g id="ID_1">%1$s</xliff:g> : <xliff:g id="ID_2">%2$s</xliff:g>"</string> <string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Ouvrir les paramètres."</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 1a69e81a8191..9a563d960cf3 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -881,20 +881,13 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"कम प्राथमिकता वाली सूचना के आइकॉन दिखाएं"</string> <string name="other" msgid="429768510980739978">"अन्य"</string> - <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) --> - <skip /> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"टाइल हटाएं"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"टाइल को आखिरी पोज़िशन पर जोड़ें"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"टाइल को किसी और पोज़िशन पर ले जाएं"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"टाइल जोड़ें"</string> + <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"टाइल को <xliff:g id="POSITION">%1$d</xliff:g> पोज़िशन पर ले जाएं"</string> + <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"टाइल को <xliff:g id="POSITION">%1$d</xliff:g> पोज़िशन पर जोड़ें"</string> + <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"टाइल की पोज़िशन <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"त्वरित सेटिंग संपादक."</string> <string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> सूचना: <xliff:g id="ID_2">%2$s</xliff:g>"</string> <string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"सेटिंग खोलें."</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 28c79fbc8852..171773c95aed 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -880,7 +880,7 @@ <string name="tuner_low_priority" msgid="8412666814123009820">"Ցուցադրել ցածր առաջնահերթության ծանուցումների պատկերակները"</string> <string name="other" msgid="429768510980739978">"Այլ"</string> <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"հեռացնել սալիկը"</string> - <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ավելացնել սալիկ վերջում"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ավելացնել սալիկը վերջում"</string> <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Տեղափոխել սալիկը"</string> <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ավելացնել սալիկ"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Տեղափոխել դիրք <xliff:g id="POSITION">%1$d</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 1f8f2aa2fc27..4a750a594dad 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -879,10 +879,10 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"Sýna tákn fyrir tilkynningar með litlum forgangi"</string> <string name="other" msgid="429768510980739978">"Annað"</string> - <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"fjarlægja reit"</string> - <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"bæta reit við aftast"</string> - <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Færa reit"</string> - <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Bæta reit við"</string> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"fjarlægja flís"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"bæta flís við aftast"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Færa flís"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Bæta flís við"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Færa í <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Bæta við í stöðu <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Staða <xliff:g id="POSITION">%1$d</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 31e6befca5ff..e6fbdb54a9de 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -890,10 +890,10 @@ <string name="tuner_low_priority" msgid="8412666814123009820">"הצגת סמלי התראות בעדיפות נמוכה"</string> <string name="other" msgid="429768510980739978">"אחר"</string> <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"הסרת האריח"</string> - <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"הוספת האריח לקצה"</string> - <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"הזזת האריח"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"הוספת האריח בסוף הרשימה"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"העברת האריח"</string> <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"הוספת אריח"</string> - <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"העברה אל <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"העברה למיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"הוספה למיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"מיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"עורך הגדרות מהירות."</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index d775823b298c..b1fab0781b2a 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -879,10 +879,10 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"Анча маанилүү эмес билдирменин сүрөтчөлөрүн көрсөтүү"</string> <string name="other" msgid="429768510980739978">"Башка"</string> - <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"плитканы өчүрүү"</string> - <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"плитканы аягына кошуу"</string> - <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Плитканы жылдыруу"</string> - <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Плитка кошуу"</string> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ыкчам баскычты өчүрүү"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ыкчам баскычты аягына кошуу"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Ыкчам баскычты жылдыруу"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ыкчам баскыч кошуу"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Төмөнкүгө жылдыруу: <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g>-позицияга кошуу"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>-позиция"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index fc6868152dc0..80f0106bbd20 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -883,8 +883,8 @@ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ടൈൽ, അവസാന ഭാഗത്ത് ചേർക്കുക"</string> <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ടൈൽ നീക്കുക"</string> <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ടൈൽ ചേർക്കുക"</string> - <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"സ്ഥാനം <xliff:g id="POSITION">%1$d</xliff:g>-ലേക്ക് നീക്കുക"</string> - <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"സ്ഥാനം <xliff:g id="POSITION">%1$d</xliff:g>-ൽ ചേർക്കുക"</string> + <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> എന്നതിലേക്ക് നീക്കുക"</string> + <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> എന്ന സ്ഥാനത്തേക്ക് ചേർക്കുക"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"സ്ഥാനം <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ദ്രുത ക്രമീകരണ എഡിറ്റർ."</string> <string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> അറിയിപ്പ്: <xliff:g id="ID_2">%2$s</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 66e3ec3ba78a..7584173d48c5 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -889,10 +889,10 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"Показывать значки уведомлений с низким приоритетом"</string> <string name="other" msgid="429768510980739978">"Другое"</string> - <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"удалить кнопку быстрого доступа"</string> - <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"добавить кнопку быстрого доступа в конец"</string> - <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Переместить кнопку быстрого доступа"</string> - <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Добавить кнопку быстрого доступа"</string> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"удалить панель"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"добавить панель в конец"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Переместить панель"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Добавить панель"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Переместить на позицию <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Добавить на позицию <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index abcde4286190..5172303032b7 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -879,20 +879,13 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"Shfaq ikonat e njoftimeve me përparësi të ulët"</string> <string name="other" msgid="429768510980739978">"Të tjera"</string> - <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) --> - <skip /> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"hiq pllakëzën"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"shto pllakëzën në fund"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Zhvendos pllakëzën"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Shto pllakëzën"</string> + <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Zhvendos te <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Shto te pozicioni <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozicioni <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redaktori i cilësimeve të shpejta."</string> <string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Njoftim nga <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string> <string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Hap cilësimet."</string> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 9d5c54fc7890..d91f1fda3f0c 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -879,20 +879,13 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"குறைந்த முன்னுரிமை உள்ள அறிவிப்பு ஐகான்களைக் காட்டு"</string> <string name="other" msgid="429768510980739978">"மற்றவை"</string> - <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) --> - <skip /> - <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) --> - <skip /> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"டைலை அகற்றும்"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"கடைசியில் டைலைச் சேர்க்கும்"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"டைலை நகர்த்து"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"டைலைச் சேர்"</string> + <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> இடத்திற்கு நகர்த்தும்"</string> + <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> என்ற இடத்தில் சேர்க்கும்"</string> + <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"இடம்: <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"விரைவு அமைப்புகள் திருத்தி."</string> <string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> அறிவிப்பு: <xliff:g id="ID_2">%2$s</xliff:g>"</string> <string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"அமைப்புகளைத் திற."</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index eeedac603ab5..26053af9936f 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -889,10 +889,10 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"Показувати значки сповіщень із низьким пріоритетом"</string> <string name="other" msgid="429768510980739978">"Інше"</string> - <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"вилучити значок"</string> - <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"додати значок у кінець"</string> - <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Перемістити значок"</string> - <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Додати значок"</string> + <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"вилучити опцію"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"додати опцію в кінець"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Перемістити опцію"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Додати опцію"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Перемістити на позицію <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додати на позицію <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиція <xliff:g id="POSITION">%1$d</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index facb47a3bdfc..dbb9e03ac279 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -880,8 +880,8 @@ <string name="tuner_low_priority" msgid="8412666814123009820">"顯示低優先順序通知圖示"</string> <string name="other" msgid="429768510980739978">"其他"</string> <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"移除圖塊"</string> - <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"將圖塊加到結尾"</string> - <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"移圖塊"</string> + <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"將圖塊加到最尾"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"移動圖塊"</string> <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"加圖塊"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"移去 <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"加去位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string> diff --git a/packages/SystemUI/res/values/config_tv.xml b/packages/SystemUI/res/values/config_tv.xml index 7451ba8e88a4..2e776749582c 100644 --- a/packages/SystemUI/res/values/config_tv.xml +++ b/packages/SystemUI/res/values/config_tv.xml @@ -15,10 +15,6 @@ --> <resources> - <!-- 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> - <!-- Whether to enable microphone disclosure indicator (com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar). --> <bool name="audio_recording_disclosure_enabled">true</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 48da5d90efe0..7cbbaf9a00ac 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -506,6 +506,10 @@ <dimen name="qs_header_tile_margin_bottom">18dp</dimen> <dimen name="qs_page_indicator_width">16dp</dimen> <dimen name="qs_page_indicator_height">8dp</dimen> + <!-- The size of a single dot in relation to the whole animation. + Scaled @dimen/qs_page_indicator-width by .4f. + --> + <dimen name="qs_page_indicator_dot_width">6.4dp</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> @@ -970,8 +974,6 @@ <!-- The start margin of quick scrub onboarding toast. --> <dimen name="recents_quick_scrub_onboarding_margin_start">8dp</dimen> - <dimen name="floating_dismiss_bottom_margin">50dp</dimen> - <dimen name="default_gear_space">18dp</dimen> <dimen name="cell_overlay_padding">18dp</dimen> @@ -1228,8 +1230,6 @@ <dimen name="bubble_dismiss_target_padding_y">20dp</dimen> <dimen name="bubble_manage_menu_elevation">4dp</dimen> - <dimen name="dismiss_target_x_size">24dp</dimen> - <!-- Bubbles user education views --> <dimen name="bubbles_manage_education_width">160dp</dimen> <!-- The inset from the top bound of the manage button to place the user education. --> @@ -1376,4 +1376,5 @@ <dimen name="media_output_dialog_header_back_icon_size">36dp</dimen> <dimen name="media_output_dialog_header_icon_padding">16dp</dimen> <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen> + <dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index d815681c8736..f2bb4907ee8f 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -175,5 +175,13 @@ <!-- Accessibility actions for PIP --> <item type="id" name="action_pip_resize" /> + + <!-- Accessibility actions for window magnification. --> + <item type="id" name="accessibility_action_zoom_in"/> + <item type="id" name="accessibility_action_zoom_out"/> + <item type="id" name="accessibility_action_move_left"/> + <item type="id" name="accessibility_action_move_right"/> + <item type="id" name="accessibility_action_move_up"/> + <item type="id" name="accessibility_action_move_down"/> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f69314977a21..2427d36076c5 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2666,6 +2666,18 @@ <string name="magnification_window_title">Magnification Window</string> <!-- Title for Magnification Controls Window [CHAR LIMIT=NONE] --> <string name="magnification_controls_title">Magnification Window Controls</string> + <!-- Action in accessibility menu to zoom in content of the magnification window. [CHAR LIMIT=30] --> + <string name="accessibility_control_zoom_in">Zoom in</string> + <!-- Action in accessibility menu to zoom out content of the magnification window. [CHAR LIMIT=30] --> + <string name="accessibility_control_zoom_out">Zoom out</string> + <!-- Action in accessibility menu to move the magnification window up. [CHAR LIMIT=30] --> + <string name="accessibility_control_move_up">Move up</string> + <!-- Action in accessibility menu to move the magnification window down. [CHAR LIMIT=30] --> + <string name="accessibility_control_move_down">Move down</string> + <!-- Action in accessibility menu to move the magnification window left. [CHAR LIMIT=30] --> + <string name="accessibility_control_move_left">Move left</string> + <!-- Action in accessibility menu to move the magnification window right. [CHAR LIMIT=30] --> + <string name="accessibility_control_move_right">Move right</string> <!-- Device Controls strings --> <!-- Device Controls empty state, title [CHAR LIMIT=30] --> @@ -2817,4 +2829,9 @@ <string name="media_output_dialog_connect_failed">Couldn\'t connect. Try again.</string> <!-- Title for pairing item [CHAR LIMIT=60] --> <string name="media_output_dialog_pairing_new">Pair new device</string> + + <!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]--> + <string name="build_number_clip_data_label">Build number</string> + <!-- Text to display when copying the build number off QS [CHAR LIMIT=NONE]--> + <string name="build_number_copy_toast">Build number copied to clipboard.</string> </resources> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 68f4b746caa2..606fd2c1848e 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -37,8 +37,6 @@ android_library { static_libs: [ "PluginCoreLib", ], - - java_version: "1.8", min_sdk_version: "26", } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index cffc10f65f1e..ee05c6cf4c47 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -373,19 +373,6 @@ public class ActivityManagerWrapper { } /** - * Moves an already resumed task to the side of the screen to initiate split screen. - */ - public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, - Rect initialBounds) { - try { - return ActivityTaskManager.getService().setTaskWindowingModeSplitScreenPrimary(taskId, - true /* onTop */); - } catch (RemoteException e) { - return false; - } - } - - /** * Registers a task stack listener with the system. * This should be called on the main thread. */ diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java index 345a649a036f..0db4faf9e493 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java @@ -86,4 +86,12 @@ public abstract class ActivityOptionsCompat { opts.setFreezeRecentTasksReordering(); return opts; } + + /** + * Sets the launch event time from launcher. + */ + public static ActivityOptions setLauncherSourceInfo(ActivityOptions opts, long uptimeMillis) { + opts.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER, uptimeMillis); + return opts; + } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java new file mode 100644 index 000000000000..27cb4f60bd9c --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java @@ -0,0 +1,71 @@ +/* + * 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.shared.system; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.view.View; + +import com.android.internal.jank.InteractionJankMonitor; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public final class InteractionJankMonitorWrapper { + // Launcher journeys. + public static final int CUJ_APP_LAUNCH_FROM_RECENTS = + InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS; + public static final int CUJ_APP_LAUNCH_FROM_ICON = + InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON; + public static final int CUJ_APP_CLOSE_TO_HOME = + InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME; + public static final int CUJ_APP_CLOSE_TO_PIP = + InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP; + public static final int CUJ_QUICK_SWITCH = + InteractionJankMonitor.CUJ_LAUNCHER_QUICK_SWITCH; + + @IntDef({ + CUJ_APP_LAUNCH_FROM_RECENTS, + CUJ_APP_LAUNCH_FROM_ICON, + CUJ_APP_CLOSE_TO_HOME, + CUJ_APP_CLOSE_TO_PIP, + CUJ_QUICK_SWITCH, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CujType { + } + + public static void init(@NonNull View view) { + InteractionJankMonitor.getInstance().init(view); + } + + public static boolean begin(@CujType int cujType) { + return InteractionJankMonitor.getInstance().begin(cujType); + } + + public static boolean begin(@CujType int cujType, long timeout) { + return InteractionJankMonitor.getInstance().begin(cujType, timeout); + } + + public static boolean end(@CujType int cujType) { + return InteractionJankMonitor.getInstance().end(cujType); + } + + public static boolean cancel(@CujType int cujType) { + return InteractionJankMonitor.getInstance().cancel(cujType); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java index 2985a61dec9e..86129e0e204e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java @@ -47,6 +47,7 @@ public class SyncRtSurfaceTransactionApplierCompat { public static final int FLAG_BACKGROUND_BLUR_RADIUS = 1 << 5; public static final int FLAG_VISIBILITY = 1 << 6; public static final int FLAG_RELATIVE_LAYER = 1 << 7; + public static final int FLAG_SHADOW_RADIUS = 1 << 8; private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0; @@ -196,6 +197,7 @@ public class SyncRtSurfaceTransactionApplierCompat { SurfaceControl relativeTo; int relativeLayer; boolean visible; + float shadowRadius; /** * @param surface The surface to modify. @@ -274,6 +276,16 @@ public class SyncRtSurfaceTransactionApplierCompat { } /** + * @param radius the Radius for the shadows to apply to the surface. + * @return this Builder + */ + public Builder withShadowRadius(float radius) { + this.shadowRadius = radius; + flags |= FLAG_SHADOW_RADIUS; + return this; + } + + /** * @param radius the Radius for blur to apply to the background surfaces. * @return this Builder */ @@ -298,31 +310,14 @@ public class SyncRtSurfaceTransactionApplierCompat { */ public SurfaceParams build() { return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer, - relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible); + relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible, + shadowRadius); } } - /** - * Constructs surface parameters to be applied when the current view state gets pushed to - * RenderThread. - * - * @param surface The surface to modify. - * @param alpha Alpha to apply. - * @param matrix Matrix to apply. - * @param windowCrop Crop to apply, only applied if not {@code null} - */ - public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix, - Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer, - float cornerRadius) { - this(surface.mSurfaceControl, - FLAG_ALL & ~(FLAG_VISIBILITY | FLAG_BACKGROUND_BLUR_RADIUS), alpha, - matrix, windowCrop, layer, relativeTo, relativeLayer, cornerRadius, - 0 /* backgroundBlurRadius */, true); - } - private SurfaceParams(SurfaceControl surface, int flags, float alpha, Matrix matrix, Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer, - float cornerRadius, int backgroundBlurRadius, boolean visible) { + float cornerRadius, int backgroundBlurRadius, boolean visible, float shadowRadius) { this.flags = flags; this.surface = surface; this.alpha = alpha; @@ -334,6 +329,7 @@ public class SyncRtSurfaceTransactionApplierCompat { this.cornerRadius = cornerRadius; this.backgroundBlurRadius = backgroundBlurRadius; this.visible = visible; + this.shadowRadius = shadowRadius; } private final int flags; @@ -349,6 +345,7 @@ public class SyncRtSurfaceTransactionApplierCompat { public final SurfaceControl relativeTo; public final int relativeLayer; public final boolean visible; + public final float shadowRadius; public void applyTo(SurfaceControl.Transaction t) { if ((flags & FLAG_MATRIX) != 0) { @@ -379,6 +376,9 @@ public class SyncRtSurfaceTransactionApplierCompat { if ((flags & FLAG_RELATIVE_LAYER) != 0) { t.setRelativeLayer(surface, relativeTo, relativeLayer); } + if ((flags & FLAG_SHADOW_RADIUS) != 0) { + t.setShadowRadius(surface, shadowRadius); + } } } } diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java index e99245fa438f..23195af8bdea 100644 --- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java +++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java @@ -33,9 +33,13 @@ import android.view.SurfaceView; import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.dagger.KeyguardBouncerScope; +import com.android.systemui.dagger.qualifiers.Main; import java.util.NoSuchElementException; +import javax.inject.Inject; + /** * Encapsulates all logic for secondary lockscreen state management. */ @@ -142,9 +146,9 @@ public class AdminSecondaryLockScreenController { } }; - public AdminSecondaryLockScreenController(Context context, ViewGroup parent, + private AdminSecondaryLockScreenController(Context context, KeyguardSecurityContainer parent, KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback, - Handler handler) { + @Main Handler handler) { mContext = context; mHandler = handler; mParent = parent; @@ -234,4 +238,26 @@ public class AdminSecondaryLockScreenController { getHolder().removeCallback(mSurfaceHolderCallback); } } + + @KeyguardBouncerScope + public static class Factory { + private final Context mContext; + private final KeyguardSecurityContainer mParent; + private final KeyguardUpdateMonitor mUpdateMonitor; + private final Handler mHandler; + + @Inject + public Factory(Context context, KeyguardSecurityContainer parent, + KeyguardUpdateMonitor updateMonitor, @Main Handler handler) { + mContext = context; + mParent = parent; + mUpdateMonitor = updateMonitor; + mHandler = handler; + } + + public AdminSecondaryLockScreenController create(KeyguardSecurityCallback callback) { + return new AdminSecondaryLockScreenController(mContext, mParent, mUpdateMonitor, + callback, mHandler); + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java index 88f4176f5eac..cc6df45c598f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -16,46 +16,26 @@ package com.android.keyguard; -import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; -import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; - import android.content.Context; -import android.content.res.ColorStateList; -import android.os.AsyncTask; -import android.os.CountDownTimer; -import android.os.SystemClock; import android.util.AttributeSet; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.View; -import android.widget.LinearLayout; -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternChecker; -import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; -import com.android.systemui.Dependency; import com.android.systemui.R; /** * Base class for PIN and password unlock screens. */ -public abstract class KeyguardAbsKeyInputView extends LinearLayout - implements KeyguardSecurityView, EmergencyButton.EmergencyButtonCallback { - protected KeyguardSecurityCallback mCallback; - protected LockPatternUtils mLockPatternUtils; - protected AsyncTask<?, ?, ?> mPendingLockCheck; - protected SecurityMessageDisplay mSecurityMessageDisplay; +public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { protected View mEcaView; protected boolean mEnableHaptics; - private boolean mDismissing; - protected boolean mResumed; - private CountDownTimer mCountdownTimer = null; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; // To avoid accidental lockout due to events while the device in in the pocket, ignore // any passwords with length less than or equal to this length. protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3; + private KeyDownListener mKeyDownListener; public KeyguardAbsKeyInputView(Context context) { this(context, null); @@ -63,38 +43,10 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) { super(context, attrs); - mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); } - @Override - public void setKeyguardCallback(KeyguardSecurityCallback callback) { - mCallback = callback; - } - - @Override - public void setLockPatternUtils(LockPatternUtils utils) { - mLockPatternUtils = utils; - mEnableHaptics = mLockPatternUtils.isTactileFeedbackEnabled(); - } - - @Override - public void reset() { - // start fresh - mDismissing = false; - resetPasswordText(false /* animate */, false /* announce */); - // if the user is currently locked out, enforce it. - long deadline = mLockPatternUtils.getLockoutAttemptDeadline( - KeyguardUpdateMonitor.getCurrentUser()); - if (shouldLockout(deadline)) { - handleAttemptLockout(deadline); - } else { - resetState(); - } - } - - // Allow subclasses to override this behavior - protected boolean shouldLockout(long deadline) { - return deadline != 0; + void setEnableHaptics(boolean enableHaptics) { + mEnableHaptics = enableHaptics; } protected abstract int getPasswordTextViewId(); @@ -102,24 +54,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout @Override protected void onFinishInflate() { - mLockPatternUtils = new LockPatternUtils(mContext); mEcaView = findViewById(R.id.keyguard_selector_fade_container); - - EmergencyButton button = findViewById(R.id.emergency_call_button); - if (button != null) { - button.setCallback(this); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this); - } - - @Override - public void onEmergencyButtonClickedWhenInCall() { - mCallback.reset(); } /* @@ -131,195 +66,14 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout return R.string.kg_wrong_password; } - protected void verifyPasswordAndUnlock() { - if (mDismissing) return; // already verified but haven't been dismissed; don't do it again. - - final LockscreenCredential password = getEnteredCredential(); - setPasswordEntryInputEnabled(false); - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - } - - final int userId = KeyguardUpdateMonitor.getCurrentUser(); - if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) { - // to avoid accidental lockout, only count attempts that are long enough to be a - // real password. This may require some tweaking. - setPasswordEntryInputEnabled(true); - onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */); - password.zeroize(); - return; - } - - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL); - LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); - } - - mKeyguardUpdateMonitor.setCredentialAttempted(); - mPendingLockCheck = LockPatternChecker.checkCredential( - mLockPatternUtils, - password, - userId, - new LockPatternChecker.OnCheckCallback() { - - @Override - public void onEarlyMatched() { - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionEnd( - ACTION_CHECK_CREDENTIAL); - } - onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */, - true /* isValidPassword */); - password.zeroize(); - } - - @Override - public void onChecked(boolean matched, int timeoutMs) { - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionEnd( - ACTION_CHECK_CREDENTIAL_UNLOCKED); - } - setPasswordEntryInputEnabled(true); - mPendingLockCheck = null; - if (!matched) { - onPasswordChecked(userId, false /* matched */, timeoutMs, - true /* isValidPassword */); - } - password.zeroize(); - } - - @Override - public void onCancelled() { - // We already got dismissed with the early matched callback, so we cancelled - // the check. However, we still need to note down the latency. - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionEnd( - ACTION_CHECK_CREDENTIAL_UNLOCKED); - } - password.zeroize(); - } - }); - } - - private void onPasswordChecked(int userId, boolean matched, int timeoutMs, - boolean isValidPassword) { - boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; - if (matched) { - mCallback.reportUnlockAttempt(userId, true, 0); - if (dismissKeyguard) { - mDismissing = true; - mCallback.dismiss(true, userId); - } - } else { - if (isValidPassword) { - mCallback.reportUnlockAttempt(userId, false, timeoutMs); - if (timeoutMs > 0) { - long deadline = mLockPatternUtils.setLockoutAttemptDeadline( - userId, timeoutMs); - handleAttemptLockout(deadline); - } - } - if (timeoutMs == 0) { - mSecurityMessageDisplay.setMessage(getWrongPasswordStringId()); - } - } - resetPasswordText(true /* animate */, !matched /* announce deletion if no match */); - } - protected abstract void resetPasswordText(boolean animate, boolean announce); protected abstract LockscreenCredential getEnteredCredential(); protected abstract void setPasswordEntryEnabled(boolean enabled); protected abstract void setPasswordEntryInputEnabled(boolean enabled); - // Prevent user from using the PIN/Password entry until scheduled deadline. - protected void handleAttemptLockout(long elapsedRealtimeDeadline) { - setPasswordEntryEnabled(false); - long elapsedRealtime = SystemClock.elapsedRealtime(); - long secondsInFuture = (long) Math.ceil( - (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); - mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { - - @Override - public void onTick(long millisUntilFinished) { - int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); - mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString( - R.plurals.kg_too_many_failed_attempts_countdown, - secondsRemaining, secondsRemaining)); - } - - @Override - public void onFinish() { - mSecurityMessageDisplay.setMessage(""); - resetState(); - } - }.start(); - } - - protected void onUserInput() { - if (mCallback != null) { - mCallback.userActivity(); - mCallback.onUserInput(); - } - mSecurityMessageDisplay.setMessage(""); - } - @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN. - // We don't want to consider it valid user input because the UI - // will already respond to the event. - if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - onUserInput(); - } - return false; - } - - @Override - public boolean needsInput() { - return false; - } - - @Override - public void onPause() { - mResumed = false; - - if (mCountdownTimer != null) { - mCountdownTimer.cancel(); - mCountdownTimer = null; - } - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - mPendingLockCheck = null; - } - reset(); - } - - @Override - public void onResume(int reason) { - mResumed = true; - } - - @Override - public KeyguardSecurityCallback getCallback() { - return mCallback; - } - - @Override - public void showPromptReason(int reason) { - if (reason != PROMPT_REASON_NONE) { - int promtReasonStringRes = getPromptReasonStringRes(reason); - if (promtReasonStringRes != 0) { - mSecurityMessageDisplay.setMessage(promtReasonStringRes); - } - } - } - - @Override - public void showMessage(CharSequence message, ColorStateList colorState) { - if (colorState != null) { - mSecurityMessageDisplay.setNextMessageColor(colorState); - } - mSecurityMessageDisplay.setMessage(message); + return mKeyDownListener != null && mKeyDownListener.onKeyDown(keyCode, event); } protected abstract int getPromptReasonStringRes(int reason); @@ -333,9 +87,12 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout } } - @Override - public boolean startDisappearAnimation(Runnable finishRunnable) { - return false; + public void setKeyDownListener(KeyDownListener keyDownListener) { + mKeyDownListener = keyDownListener; + } + + public interface KeyDownListener { + boolean onKeyDown(int keyCode, KeyEvent keyEvent); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java new file mode 100644 index 000000000000..53f847434dcc --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -0,0 +1,275 @@ +/* + * 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.keyguard; + +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; +import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT; + +import android.content.res.ColorStateList; +import android.os.AsyncTask; +import android.os.CountDownTimer; +import android.os.SystemClock; +import android.view.KeyEvent; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternChecker; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockscreenCredential; +import com.android.keyguard.EmergencyButton.EmergencyButtonCallback; +import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; + +public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKeyInputView> + extends KeyguardInputViewController<T> { + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final LockPatternUtils mLockPatternUtils; + private final LatencyTracker mLatencyTracker; + private CountDownTimer mCountdownTimer; + protected KeyguardMessageAreaController mMessageAreaController; + private boolean mDismissing; + protected AsyncTask<?, ?, ?> mPendingLockCheck; + protected boolean mResumed; + + private final KeyDownListener mKeyDownListener = (keyCode, keyEvent) -> { + // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN. + // We don't want to consider it valid user input because the UI + // will already respond to the event. + if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { + onUserInput(); + } + return false; + }; + + private final EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() { + @Override + public void onEmergencyButtonClickedWhenInCall() { + getKeyguardSecurityCallback().reset(); + } + }; + + protected KeyguardAbsKeyInputViewController(T view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, + LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + LatencyTracker latencyTracker) { + super(view, securityMode, keyguardSecurityCallback); + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mLockPatternUtils = lockPatternUtils; + mLatencyTracker = latencyTracker; + KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView); + mMessageAreaController = messageAreaControllerFactory.create(kma); + } + + abstract void resetState(); + + @Override + public void init() { + super.init(); + mMessageAreaController.init(); + } + + @Override + protected void onViewAttached() { + mView.setKeyDownListener(mKeyDownListener); + mView.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); + EmergencyButton button = mView.findViewById(R.id.emergency_call_button); + if (button != null) { + button.setCallback(mEmergencyButtonCallback); + } + } + + @Override + public void reset() { + // start fresh + mDismissing = false; + mView.resetPasswordText(false /* animate */, false /* announce */); + // if the user is currently locked out, enforce it. + long deadline = mLockPatternUtils.getLockoutAttemptDeadline( + KeyguardUpdateMonitor.getCurrentUser()); + if (shouldLockout(deadline)) { + handleAttemptLockout(deadline); + } else { + resetState(); + } + } + + @Override + public boolean needsInput() { + return false; + } + + @Override + public void showMessage(CharSequence message, ColorStateList colorState) { + if (colorState != null) { + mMessageAreaController.setNextMessageColor(colorState); + } + mMessageAreaController.setMessage(message); + } + + // Allow subclasses to override this behavior + protected boolean shouldLockout(long deadline) { + return deadline != 0; + } + + // Prevent user from using the PIN/Password entry until scheduled deadline. + protected void handleAttemptLockout(long elapsedRealtimeDeadline) { + mView.setPasswordEntryEnabled(false); + long elapsedRealtime = SystemClock.elapsedRealtime(); + long secondsInFuture = (long) Math.ceil( + (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); + mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { + + @Override + public void onTick(long millisUntilFinished) { + int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); + mMessageAreaController.setMessage(mView.getResources().getQuantityString( + R.plurals.kg_too_many_failed_attempts_countdown, + secondsRemaining, secondsRemaining)); + } + + @Override + public void onFinish() { + mMessageAreaController.setMessage(""); + resetState(); + } + }.start(); + } + + void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) { + boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; + if (matched) { + getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0); + if (dismissKeyguard) { + mDismissing = true; + getKeyguardSecurityCallback().dismiss(true, userId); + } + } else { + if (isValidPassword) { + getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs); + if (timeoutMs > 0) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline( + userId, timeoutMs); + handleAttemptLockout(deadline); + } + } + if (timeoutMs == 0) { + mMessageAreaController.setMessage(mView.getWrongPasswordStringId()); + } + } + mView.resetPasswordText(true /* animate */, !matched /* announce deletion if no match */); + } + + protected void verifyPasswordAndUnlock() { + if (mDismissing) return; // already verified but haven't been dismissed; don't do it again. + + final LockscreenCredential password = mView.getEnteredCredential(); + mView.setPasswordEntryInputEnabled(false); + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + } + + final int userId = KeyguardUpdateMonitor.getCurrentUser(); + if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) { + // to avoid accidental lockout, only count attempts that are long enough to be a + // real password. This may require some tweaking. + mView.setPasswordEntryInputEnabled(true); + onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */); + password.zeroize(); + return; + } + + mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL); + mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); + + mKeyguardUpdateMonitor.setCredentialAttempted(); + mPendingLockCheck = LockPatternChecker.checkCredential( + mLockPatternUtils, + password, + userId, + new LockPatternChecker.OnCheckCallback() { + + @Override + public void onEarlyMatched() { + mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL); + + onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */, + true /* isValidPassword */); + password.zeroize(); + } + + @Override + public void onChecked(boolean matched, int timeoutMs) { + mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED); + mView.setPasswordEntryInputEnabled(true); + mPendingLockCheck = null; + if (!matched) { + onPasswordChecked(userId, false /* matched */, timeoutMs, + true /* isValidPassword */); + } + password.zeroize(); + } + + @Override + public void onCancelled() { + // We already got dismissed with the early matched callback, so we cancelled + // the check. However, we still need to note down the latency. + mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED); + password.zeroize(); + } + }); + } + + @Override + public void showPromptReason(int reason) { + if (reason != PROMPT_REASON_NONE) { + int promtReasonStringRes = mView.getPromptReasonStringRes(reason); + if (promtReasonStringRes != 0) { + mMessageAreaController.setMessage(promtReasonStringRes); + } + } + } + + protected void onUserInput() { + getKeyguardSecurityCallback().userActivity(); + getKeyguardSecurityCallback().onUserInput(); + mMessageAreaController.setMessage(""); + } + + @Override + public void onResume(int reason) { + mResumed = true; + } + + @Override + public void onPause() { + mResumed = false; + + if (mCountdownTimer != null) { + mCountdownTimer.cancel(); + mCountdownTimer = null; + } + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + mPendingLockCheck = null; + } + reset(); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index be21d203411e..36d5543f1c01 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -39,7 +39,6 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; -import com.android.systemui.util.InjectionInflationController; import javax.inject.Inject; @@ -49,7 +48,6 @@ public class KeyguardDisplayManager { private final MediaRouter mMediaRouter; private final DisplayManager mDisplayService; - private final InjectionInflationController mInjectableInflater; private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; private final Context mContext; @@ -92,10 +90,8 @@ public class KeyguardDisplayManager { @Inject public KeyguardDisplayManager(Context context, - InjectionInflationController injectableInflater, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) { mContext = context; - mInjectableInflater = injectableInflater; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; mMediaRouter = mContext.getSystemService(MediaRouter.class); mDisplayService = mContext.getSystemService(DisplayManager.class); @@ -131,8 +127,7 @@ public class KeyguardDisplayManager { Presentation presentation = mPresentations.get(displayId); if (presentation == null) { final Presentation newPresentation = new KeyguardPresentation(mContext, display, - mKeyguardStatusViewComponentFactory, - mInjectableInflater.injectable(LayoutInflater.from(mContext))); + mKeyguardStatusViewComponentFactory, LayoutInflater.from(mContext)); newPresentation.setOnDismissListener(dialog -> { if (newPresentation.equals(mPresentations.get(displayId))) { mPresentations.remove(displayId); @@ -250,7 +245,7 @@ public class KeyguardDisplayManager { private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; - private final LayoutInflater mInjectableLayoutInflater; + private final LayoutInflater mLayoutInflater; private KeyguardClockSwitchController mKeyguardClockSwitchController; private View mClock; private int mUsableWidth; @@ -270,10 +265,10 @@ public class KeyguardDisplayManager { KeyguardPresentation(Context context, Display display, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, - LayoutInflater injectionLayoutInflater) { + LayoutInflater layoutInflater) { super(context, display, R.style.Theme_SystemUI_KeyguardPresentation); mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; - mInjectableLayoutInflater = injectionLayoutInflater; + mLayoutInflater = layoutInflater; getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); setCancelable(false); } @@ -299,7 +294,7 @@ public class KeyguardDisplayManager { mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200; mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200; - setContentView(mInjectableLayoutInflater.inflate(R.layout.keyguard_presentation, null)); + setContentView(mLayoutInflater.inflate(R.layout.keyguard_presentation, null)); // Logic to make the lock screen fullscreen getWindow().getDecorView().setSystemUiVisibility( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java index 7aabb17de90c..351369c51364 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -163,33 +163,34 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView> @Inject public KeyguardHostViewController(KeyguardHostView view, KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardSecurityContainerController keyguardSecurityContainerController, AudioManager audioManager, TelephonyManager telephonyManager, - ViewMediatorCallback viewMediatorCallback) { + ViewMediatorCallback viewMediatorCallback, + KeyguardSecurityContainerController.Factory + keyguardSecurityContainerControllerFactory) { super(view); mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mKeyguardSecurityContainerController = keyguardSecurityContainerController; mAudioManager = audioManager; mTelephonyManager = telephonyManager; mViewMediatorCallback = viewMediatorCallback; + mKeyguardSecurityContainerController = keyguardSecurityContainerControllerFactory.create( + mSecurityCallback); } /** Initialize the Controller. */ public void init() { super.init(); - mView.setViewMediatorCallback(mViewMediatorCallback); - // Update ViewMediator with the current input method requirements - mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput()); mKeyguardSecurityContainerController.init(); - mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback); - mKeyguardSecurityContainerController.showPrimarySecurityScreen(false); } @Override protected void onViewAttached() { + mView.setViewMediatorCallback(mViewMediatorCallback); + // Update ViewMediator with the current input method requirements + mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput()); mKeyguardUpdateMonitor.registerCallback(mUpdateCallback); mView.setOnKeyListener(mOnKeyListener); + mKeyguardSecurityContainerController.showPrimarySecurityScreen(false); } @Override @@ -350,7 +351,7 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView> } public boolean handleBackKey() { - if (mKeyguardSecurityContainerController.getCurrentSecuritySelection() + if (mKeyguardSecurityContainerController.getCurrentSecurityMode() != SecurityMode.None) { mKeyguardSecurityContainerController.dismiss( false, KeyguardUpdateMonitor.getCurrentUser()); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java new file mode 100644 index 000000000000..d42a53cc875e --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.LinearLayout; + +import androidx.annotation.Nullable; + +/** + * A Base class for all Keyguard password/pattern/pin related inputs. + */ +public abstract class KeyguardInputView extends LinearLayout { + + public KeyguardInputView(Context context) { + super(context); + } + + public KeyguardInputView(Context context, + @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public KeyguardInputView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + abstract CharSequence getTitle(); + + boolean disallowInterceptTouch(MotionEvent event) { + return false; + } + + void startAppearAnimation() {} + + boolean startDisappearAnimation(Runnable finishRunnable) { + return false; + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java new file mode 100644 index 000000000000..fbda818740e8 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard; + +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.telephony.TelephonyManager; +import android.view.inputmethod.InputMethodManager; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.ViewController; +import com.android.systemui.util.concurrency.DelayableExecutor; + +import javax.inject.Inject; + + +/** Controller for a {@link KeyguardSecurityView}. */ +public abstract class KeyguardInputViewController<T extends KeyguardInputView> + extends ViewController<T> implements KeyguardSecurityView { + + private final SecurityMode mSecurityMode; + private final KeyguardSecurityCallback mKeyguardSecurityCallback; + private boolean mPaused; + + + // The following is used to ignore callbacks from SecurityViews that are no longer current + // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the + // state for the current security method. + private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() { + @Override + public void userActivity() { } + @Override + public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { } + @Override + public boolean isVerifyUnlockOnly() { + return false; + } + @Override + public void dismiss(boolean securityVerified, int targetUserId) { } + @Override + public void dismiss(boolean authenticated, int targetId, + boolean bypassSecondaryLockScreen) { } + @Override + public void onUserInput() { } + @Override + public void reset() {} + }; + + protected KeyguardInputViewController(T view, SecurityMode securityMode, + KeyguardSecurityCallback keyguardSecurityCallback) { + super(view); + mSecurityMode = securityMode; + mKeyguardSecurityCallback = keyguardSecurityCallback; + } + + @Override + protected void onViewAttached() { + } + + @Override + protected void onViewDetached() { + } + + SecurityMode getSecurityMode() { + return mSecurityMode; + } + + protected KeyguardSecurityCallback getKeyguardSecurityCallback() { + if (mPaused) { + return mNullCallback; + } + + return mKeyguardSecurityCallback; + } + + @Override + public void reset() { + } + + @Override + public void onPause() { + mPaused = true; + } + + @Override + public void onResume(int reason) { + mPaused = false; + } + + @Override + public void showPromptReason(int reason) { + } + + @Override + public void showMessage(CharSequence message, ColorStateList colorState) { + } + + public void startAppearAnimation() { + mView.startAppearAnimation(); + } + + public boolean startDisappearAnimation(Runnable finishRunnable) { + return mView.startDisappearAnimation(finishRunnable); + } + + @Override + public CharSequence getTitle() { + return mView.getTitle(); + } + + /** Finds the index of this view in the suppplied parent view. */ + public int getIndexIn(KeyguardSecurityViewFlipper view) { + return view.indexOfChild(mView); + } + + /** Factory for a {@link KeyguardInputViewController}. */ + public static class Factory { + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final LockPatternUtils mLockPatternUtils; + private final LatencyTracker mLatencyTracker; + private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory; + private final InputMethodManager mInputMethodManager; + private final DelayableExecutor mMainExecutor; + private final Resources mResources; + private LiftToActivateListener mLiftToActivateListener; + private TelephonyManager mTelephonyManager; + + @Inject + public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, + LockPatternUtils lockPatternUtils, + LatencyTracker latencyTracker, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor, + @Main Resources resources, LiftToActivateListener liftToActivateListener, + TelephonyManager telephonyManager) { + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mLockPatternUtils = lockPatternUtils; + mLatencyTracker = latencyTracker; + mMessageAreaControllerFactory = messageAreaControllerFactory; + mInputMethodManager = inputMethodManager; + mMainExecutor = mainExecutor; + mResources = resources; + mLiftToActivateListener = liftToActivateListener; + mTelephonyManager = telephonyManager; + } + + /** Create a new {@link KeyguardInputViewController}. */ + public KeyguardInputViewController create(KeyguardInputView keyguardInputView, + SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback) { + if (keyguardInputView instanceof KeyguardPatternView) { + return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView, + mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, + keyguardSecurityCallback, mLatencyTracker, mMessageAreaControllerFactory); + } else if (keyguardInputView instanceof KeyguardPasswordView) { + return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView, + mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, + keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, + mInputMethodManager, mMainExecutor, mResources); + } else if (keyguardInputView instanceof KeyguardPINView) { + return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, + mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, + keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, + mLiftToActivateListener); + } else if (keyguardInputView instanceof KeyguardSimPinView) { + return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, + mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, + keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, + mLiftToActivateListener, mTelephonyManager); + } else if (keyguardInputView instanceof KeyguardSimPukView) { + return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, + mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, + keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, + mLiftToActivateListener, mTelephonyManager); + } + + throw new RuntimeException("Unable to find controller for " + keyguardInputView); + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java index a8b1451d92c7..1a0a4370fca4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java @@ -16,8 +16,6 @@ package com.android.keyguard; -import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; - import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; @@ -31,20 +29,14 @@ import android.util.TypedValue; import android.view.View; import android.widget.TextView; -import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.statusbar.policy.ConfigurationController; import java.lang.ref.WeakReference; -import javax.inject.Inject; -import javax.inject.Named; - /*** * Manages a number of views inside of the given layout. See below for a list of widgets. */ -public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay, - ConfigurationController.ConfigurationListener { +public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay { /** Handler token posted with accessibility announcement runnables. */ private static final Object ANNOUNCE_TOKEN = new Object(); @@ -56,71 +48,26 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp private static final int DEFAULT_COLOR = -1; private final Handler mHandler; - private final ConfigurationController mConfigurationController; private ColorStateList mDefaultColorState; private CharSequence mMessage; private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR); private boolean mBouncerVisible; - private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { - public void onFinishedGoingToSleep(int why) { - setSelected(false); - } - - public void onStartedWakingUp() { - setSelected(true); - } - - @Override - public void onKeyguardBouncerChanged(boolean bouncer) { - mBouncerVisible = bouncer; - update(); - } - }; - - public KeyguardMessageArea(Context context) { - super(context, null); - throw new IllegalStateException("This constructor should never be invoked"); - } - - @Inject - public KeyguardMessageArea(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, - ConfigurationController configurationController) { - this(context, attrs, Dependency.get(KeyguardUpdateMonitor.class), configurationController); - } - - public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor, - ConfigurationController configurationController) { + public KeyguardMessageArea(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug - monitor.registerCallback(mInfoCallback); mHandler = new Handler(Looper.myLooper()); - mConfigurationController = configurationController; onThemeChanged(); } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mConfigurationController.addCallback(this); - onThemeChanged(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mConfigurationController.removeCallback(this); - } - - @Override public void setNextMessageColor(ColorStateList colorState) { mNextMessageColorState = colorState; } - @Override - public void onThemeChanged() { + void onThemeChanged() { TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor }); @@ -130,8 +77,7 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp update(); } - @Override - public void onDensityOrFontScaleChanged() { + void onDensityOrFontScaleChanged() { TypedArray array = mContext.obtainStyledAttributes(R.style.Keyguard_TextView, new int[] { android.R.attr.textSize }); @@ -177,12 +123,6 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp return messageArea; } - @Override - protected void onFinishInflate() { - boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive(); - setSelected(shouldMarquee); // This is required to ensure marquee works - } - private void securityMessageChanged(CharSequence message) { mMessage = message; update(); @@ -196,7 +136,7 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp update(); } - private void update() { + void update() { CharSequence status = mMessage; setVisibility(TextUtils.isEmpty(status) || !mBouncerVisible ? INVISIBLE : VISIBLE); setText(status); @@ -208,6 +148,9 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp setTextColor(colorState); } + public void setBouncerVisible(boolean bouncerVisible) { + mBouncerVisible = bouncerVisible; + } /** * Runnable used to delay accessibility announcements. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java index f056bdbb9706..1618e8e58055 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -16,7 +16,10 @@ package com.android.keyguard; +import android.content.res.ColorStateList; + import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.util.ViewController; import javax.inject.Inject; @@ -26,6 +29,35 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; + + private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { + public void onFinishedGoingToSleep(int why) { + mView.setSelected(false); + } + + public void onStartedWakingUp() { + mView.setSelected(true); + } + + @Override + public void onKeyguardBouncerChanged(boolean bouncer) { + mView.setBouncerVisible(bouncer); + mView.update(); + } + }; + + private ConfigurationListener mConfigurationListener = new ConfigurationListener() { + @Override + public void onThemeChanged() { + mView.onThemeChanged(); + } + + @Override + public void onDensityOrFontScaleChanged() { + mView.onDensityOrFontScaleChanged(); + } + }; + private KeyguardMessageAreaController(KeyguardMessageArea view, KeyguardUpdateMonitor keyguardUpdateMonitor, ConfigurationController configurationController) { @@ -37,17 +69,31 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag @Override protected void onViewAttached() { - //mConfigurationController.addCallback(); - //mKeyguardUpdateMonitor.registerCallback(); + mConfigurationController.addCallback(mConfigurationListener); + mKeyguardUpdateMonitor.registerCallback(mInfoCallback); + mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive()); + mView.onThemeChanged(); } @Override protected void onViewDetached() { - //mConfigurationController.removeCallback(); - //mKeyguardUpdateMonitor.removeCallback(); + mConfigurationController.removeCallback(mConfigurationListener); + mKeyguardUpdateMonitor.removeCallback(mInfoCallback); + } + + public void setMessage(CharSequence s) { + mView.setMessage(s); + } + + public void setMessage(int resId) { + mView.setMessage(resId); + } + + public void setNextMessageColor(ColorStateList colorState) { + mView.setNextMessageColor(colorState); } - /** Factory for createing {@link com.android.keyguard.KeyguardMessageAreaController}. */ + /** Factory for creating {@link com.android.keyguard.KeyguardMessageAreaController}. */ public static class Factory { private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index 12ea1d586e10..580d7043a220 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -24,7 +24,6 @@ import android.view.animation.AnimationUtils; import com.android.settingslib.animation.AppearAnimationUtils; import com.android.settingslib.animation.DisappearAnimationUtils; -import com.android.systemui.Dependency; import com.android.systemui.R; /** @@ -40,10 +39,8 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { private ViewGroup mRow1; private ViewGroup mRow2; private ViewGroup mRow3; - private View mDivider; private int mDisappearYTranslation; private View[][] mViews; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; public KeyguardPINView(Context context) { this(context, null); @@ -63,15 +60,10 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { mContext, android.R.interpolator.fast_out_linear_in)); mDisappearYTranslation = getResources().getDimensionPixelSize( R.dimen.disappear_y_translation); - mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); } @Override protected void resetState() { - super.resetState(); - if (mSecurityMessageDisplay != null) { - mSecurityMessageDisplay.setMessage(""); - } } @Override @@ -88,7 +80,6 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { mRow1 = findViewById(R.id.row1); mRow2 = findViewById(R.id.row2); mRow3 = findViewById(R.id.row3); - mDivider = findViewById(R.id.divider); mViews = new View[][]{ new View[]{ mRow0, null, null @@ -112,18 +103,6 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { new View[]{ null, mEcaView, null }}; - - View cancelBtn = findViewById(R.id.cancel_button); - if (cancelBtn != null) { - cancelBtn.setOnClickListener(view -> { - mCallback.reset(); - mCallback.onCancelClicked(); - }); - } - } - - @Override - public void showUsabilityHint() { } @Override @@ -147,24 +126,21 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { }); } - @Override - public boolean startDisappearAnimation(final Runnable finishRunnable) { + public boolean startDisappearAnimation(boolean needsSlowUnlockTransition, + final Runnable finishRunnable) { + enableClipping(false); setTranslationY(0); AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 280 /* duration */, mDisappearYTranslation, mDisappearAnimationUtils.getInterpolator()); - DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor - .needsSlowUnlockTransition() + DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils; disappearAnimationUtils.startAnimation2d(mViews, - new Runnable() { - @Override - public void run() { - enableClipping(true); - if (finishRunnable != null) { - finishRunnable.run(); - } + () -> { + enableClipping(true); + if (finishRunnable != null) { + finishRunnable.run(); } }); return true; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 97317cf5580f..aaa5efec807e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -16,50 +16,37 @@ package com.android.keyguard; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; + import android.content.Context; import android.graphics.Rect; -import android.os.UserHandle; -import android.text.Editable; -import android.text.InputType; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.text.method.TextKeyListener; import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; import android.widget.TextView; -import android.widget.TextView.OnEditorActionListener; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.TextViewInputDisabler; import com.android.systemui.R; - -import java.util.List; /** * Displays an alphanumeric (latin-1) key entry for the user to enter * an unlock password */ -public class KeyguardPasswordView extends KeyguardAbsKeyInputView - implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { +public class KeyguardPasswordView extends KeyguardAbsKeyInputView { - private final boolean mShowImeAtScreenOn; private final int mDisappearYTranslation; // A delay constant to be used in a workaround for the situation where InputMethodManagerService // is not switched to the new user yet. // TODO: Remove this by ensuring such a race condition never happens. - private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms - InputMethodManager mImm; private TextView mPasswordEntry; private TextViewInputDisabler mPasswordEntryDisabler; - private View mSwitchImeButton; private Interpolator mLinearOutSlowInInterpolator; private Interpolator mFastOutLinearInInterpolator; @@ -70,8 +57,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView public KeyguardPasswordView(Context context, AttributeSet attrs) { super(context, attrs); - mShowImeAtScreenOn = context.getResources(). - getBoolean(R.bool.kg_show_ime_at_screen_on); mDisappearYTranslation = getResources().getDimensionPixelSize( R.dimen.disappear_y_translation); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( @@ -82,20 +67,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView @Override protected void resetState() { - mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); - if (mSecurityMessageDisplay != null) { - mSecurityMessageDisplay.setMessage(""); - } - final boolean wasDisabled = mPasswordEntry.isEnabled(); - setPasswordEntryEnabled(true); - setPasswordEntryInputEnabled(true); - // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage. - if (!mResumed || !mPasswordEntry.isVisibleToUser()) { - return; - } - if (wasDisabled) { - mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); - } } @Override @@ -104,29 +75,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView } @Override - public boolean needsInput() { - return true; - } - - @Override - public void onResume(final int reason) { - super.onResume(reason); - - // Wait a bit to focus the field so the focusable flag on the window is already set then. - post(new Runnable() { - @Override - public void run() { - if (isShown() && mPasswordEntry.isEnabled()) { - mPasswordEntry.requestFocus(); - if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) { - mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); - } - } - } - }); - } - - @Override protected int getPromptReasonStringRes(int reason) { switch (reason) { case PROMPT_REASON_RESTART: @@ -146,97 +94,13 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView } } - @Override - public void onPause() { - super.onPause(); - mImm.hideSoftInputFromWindow(getWindowToken(), 0); - } - - @Override - public void onStartingToHide() { - mImm.hideSoftInputFromWindow(getWindowToken(), 0); - } - - private void updateSwitchImeButton() { - // If there's more than one IME, enable the IME switcher button - final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE; - final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes(mImm, false); - if (wasVisible != shouldBeVisible) { - mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE); - } - - // TODO: Check if we still need this hack. - // If no icon is visible, reset the start margin on the password field so the text is - // still centered. - if (mSwitchImeButton.getVisibility() != View.VISIBLE) { - android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams(); - if (params instanceof MarginLayoutParams) { - final MarginLayoutParams mlp = (MarginLayoutParams) params; - mlp.setMarginStart(0); - mPasswordEntry.setLayoutParams(params); - } - } - } @Override protected void onFinishInflate() { super.onFinishInflate(); - mImm = (InputMethodManager) getContext().getSystemService( - Context.INPUT_METHOD_SERVICE); - mPasswordEntry = findViewById(getPasswordTextViewId()); - mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry); - mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); - mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_VARIATION_PASSWORD); - mPasswordEntry.setOnEditorActionListener(this); - mPasswordEntry.addTextChangedListener(this); - - // Poke the wakelock any time the text is selected or modified - mPasswordEntry.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mCallback.userActivity(); - } - }); - - // Set selected property on so the view can send accessibility events. - mPasswordEntry.setSelected(true); - - mSwitchImeButton = findViewById(R.id.switch_ime_button); - mSwitchImeButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mCallback.userActivity(); // Leave the screen on a bit longer - // Do not show auxiliary subtypes in password lock screen. - mImm.showInputMethodPickerFromSystem(false /* showAuxiliarySubtypes */, - getContext().getDisplayId()); - } - }); - - View cancelBtn = findViewById(R.id.cancel_button); - if (cancelBtn != null) { - cancelBtn.setOnClickListener(view -> { - mCallback.reset(); - mCallback.onCancelClicked(); - }); - } - - // If there's more than one IME, enable the IME switcher button - updateSwitchImeButton(); - - // When we the current user is switching, InputMethodManagerService sometimes has not - // switched internal state yet here. As a quick workaround, we check the keyboard state - // again. - // TODO: Remove this workaround by ensuring such a race condition never happens. - postDelayed(new Runnable() { - @Override - public void run() { - updateSwitchImeButton(); - } - }, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON); } @Override @@ -265,59 +129,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView mPasswordEntryDisabler.setInputEnabled(enabled); } - /** - * Method adapted from com.android.inputmethod.latin.Utils - * - * @param imm The input method manager - * @param shouldIncludeAuxiliarySubtypes - * @return true if we have multiple IMEs to choose from - */ - private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, - final boolean shouldIncludeAuxiliarySubtypes) { - final List<InputMethodInfo> enabledImis = - imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser()); - - // Number of the filtered IMEs - int filteredImisCount = 0; - - for (InputMethodInfo imi : enabledImis) { - // We can return true immediately after we find two or more filtered IMEs. - if (filteredImisCount > 1) return true; - final List<InputMethodSubtype> subtypes = - imm.getEnabledInputMethodSubtypeList(imi, true); - // IMEs that have no subtypes should be counted. - if (subtypes.isEmpty()) { - ++filteredImisCount; - continue; - } - - int auxCount = 0; - for (InputMethodSubtype subtype : subtypes) { - if (subtype.isAuxiliary()) { - ++auxCount; - } - } - final int nonAuxCount = subtypes.size() - auxCount; - - // IMEs that have one or more non-auxiliary subtypes should be counted. - // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary - // subtypes should be counted as well. - if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { - ++filteredImisCount; - continue; - } - } - - return filteredImisCount > 1 - // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled - // input method subtype (The current IME should be LatinIME.) - || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; - } - - @Override - public void showUsabilityHint() { - } - @Override public int getWrongPasswordStringId() { return R.string.kg_wrong_password; @@ -346,45 +157,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView } @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - if (mCallback != null) { - mCallback.userActivity(); - } - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - // Poor man's user edit detection, assuming empty text is programmatic and everything else - // is from the user. - if (!TextUtils.isEmpty(s)) { - onUserInput(); - } - } - - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - // Check if this was the result of hitting the enter key - final boolean isSoftImeEvent = event == null - && (actionId == EditorInfo.IME_NULL - || actionId == EditorInfo.IME_ACTION_DONE - || actionId == EditorInfo.IME_ACTION_NEXT); - final boolean isKeyboardEnterKey = event != null - && KeyEvent.isConfirmKey(event.getKeyCode()) - && event.getAction() == KeyEvent.ACTION_DOWN; - if (isSoftImeEvent || isKeyboardEnterKey) { - verifyPasswordAndUnlock(); - return true; - } - return false; - } - - @Override public CharSequence getTitle() { - return getContext().getString( + return getResources().getString( com.android.internal.R.string.keyguard_accessibility_password_unlock); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java new file mode 100644 index 000000000000..d34ea8c5e018 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -0,0 +1,275 @@ +/* + * 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.keyguard; + +import android.content.res.Resources; +import android.os.UserHandle; +import android.text.Editable; +import android.text.InputType; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.TextKeyListener; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.concurrency.DelayableExecutor; + +import java.util.List; + +public class KeyguardPasswordViewController + extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> { + + private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms + + private final KeyguardSecurityCallback mKeyguardSecurityCallback; + private final InputMethodManager mInputMethodManager; + private final DelayableExecutor mMainExecutor; + private final boolean mShowImeAtScreenOn; + private TextView mPasswordEntry; + private View mSwitchImeButton; + + private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> { + // Check if this was the result of hitting the enter key + final boolean isSoftImeEvent = event == null + && (actionId == EditorInfo.IME_NULL + || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT); + final boolean isKeyboardEnterKey = event != null + && KeyEvent.isConfirmKey(event.getKeyCode()) + && event.getAction() == KeyEvent.ACTION_DOWN; + if (isSoftImeEvent || isKeyboardEnterKey) { + verifyPasswordAndUnlock(); + return true; + } + return false; + }; + + private final TextWatcher mTextWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + mKeyguardSecurityCallback.userActivity(); + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + if (!TextUtils.isEmpty(s)) { + onUserInput(); + } + } + }; + + protected KeyguardPasswordViewController(KeyguardPasswordView view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, + LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + LatencyTracker latencyTracker, + InputMethodManager inputMethodManager, + @Main DelayableExecutor mainExecutor, + @Main Resources resources) { + super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, + messageAreaControllerFactory, latencyTracker); + mKeyguardSecurityCallback = keyguardSecurityCallback; + mInputMethodManager = inputMethodManager; + mMainExecutor = mainExecutor; + mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on); + mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); + mSwitchImeButton = mView.findViewById(R.id.switch_ime_button); + } + + @Override + protected void onViewAttached() { + super.onViewAttached(); + mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); + mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); + mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_PASSWORD); + + // Set selected property on so the view can send accessibility events. + mPasswordEntry.setSelected(true); + mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener); + mPasswordEntry.addTextChangedListener(mTextWatcher); + // Poke the wakelock any time the text is selected or modified + mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity()); + + mSwitchImeButton.setOnClickListener(v -> { + mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer + // Do not show auxiliary subtypes in password lock screen. + mInputMethodManager.showInputMethodPickerFromSystem(false, + mView.getContext().getDisplayId()); + }); + + View cancelBtn = mView.findViewById(R.id.cancel_button); + if (cancelBtn != null) { + cancelBtn.setOnClickListener(view -> { + mKeyguardSecurityCallback.reset(); + mKeyguardSecurityCallback.onCancelClicked(); + }); + } + + // If there's more than one IME, enable the IME switcher button + updateSwitchImeButton(); + + // When we the current user is switching, InputMethodManagerService sometimes has not + // switched internal state yet here. As a quick workaround, we check the keyboard state + // again. + // TODO: Remove this workaround by ensuring such a race condition never happens. + mMainExecutor.executeDelayed( + this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON); + } + + @Override + protected void onViewDetached() { + super.onViewDetached(); + mPasswordEntry.setOnEditorActionListener(null); + } + + @Override + public boolean needsInput() { + return true; + } + + @Override + void resetState() { + mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); + mMessageAreaController.setMessage(""); + final boolean wasDisabled = mPasswordEntry.isEnabled(); + mView.setPasswordEntryEnabled(true); + mView.setPasswordEntryInputEnabled(true); + // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage. + if (!mResumed || !mPasswordEntry.isVisibleToUser()) { + return; + } + if (wasDisabled) { + mInputMethodManager.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); + } + } + + @Override + public void onResume(int reason) { + super.onResume(reason); + // Wait a bit to focus the field so the focusable flag on the window is already set then. + mMainExecutor.execute(() -> { + if (mView.isShown() && mPasswordEntry.isEnabled()) { + mPasswordEntry.requestFocus(); + if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) { + mInputMethodManager.showSoftInput( + mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); + } + } + }); + } + + @Override + public void onPause() { + super.onPause(); + mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0); + } + + @Override + public void onStartingToHide() { + mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0); + } + + private void updateSwitchImeButton() { + // If there's more than one IME, enable the IME switcher button + final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE; + final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes( + mInputMethodManager, false); + if (wasVisible != shouldBeVisible) { + mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE); + } + + // TODO: Check if we still need this hack. + // If no icon is visible, reset the start margin on the password field so the text is + // still centered. + if (mSwitchImeButton.getVisibility() != View.VISIBLE) { + android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams(); + if (params instanceof MarginLayoutParams) { + final MarginLayoutParams mlp = (MarginLayoutParams) params; + mlp.setMarginStart(0); + mPasswordEntry.setLayoutParams(params); + } + } + } + + /** + * Method adapted from com.android.inputmethod.latin.Utils + * + * @param imm The input method manager + * @param shouldIncludeAuxiliarySubtypes + * @return true if we have multiple IMEs to choose from + */ + private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, + final boolean shouldIncludeAuxiliarySubtypes) { + final List<InputMethodInfo> enabledImis = + imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser()); + + // Number of the filtered IMEs + int filteredImisCount = 0; + + for (InputMethodInfo imi : enabledImis) { + // We can return true immediately after we find two or more filtered IMEs. + if (filteredImisCount > 1) return true; + final List<InputMethodSubtype> subtypes = + imm.getEnabledInputMethodSubtypeList(imi, true); + // IMEs that have no subtypes should be counted. + if (subtypes.isEmpty()) { + ++filteredImisCount; + continue; + } + + int auxCount = 0; + for (InputMethodSubtype subtype : subtypes) { + if (subtype.isAuxiliary()) { + ++auxCount; + } + } + final int nonAuxCount = subtypes.size() - auxCount; + + // IMEs that have one or more non-auxiliary subtypes should be counted. + // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary + // subtypes should be counted as well. + if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { + ++filteredImisCount; + continue; + } + } + + return filteredImisCount > 1 + // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's + //enabled input method subtype (The current IME should be LatinIME.) + || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index c4a9fcb45284..bdcf467c2456 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -15,62 +15,39 @@ */ package com.android.keyguard; -import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; -import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; - import android.content.Context; -import android.content.res.ColorStateList; import android.graphics.Rect; -import android.os.AsyncTask; -import android.os.CountDownTimer; import android.os.SystemClock; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; -import android.widget.LinearLayout; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternChecker; -import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternView; -import com.android.internal.widget.LockscreenCredential; import com.android.settingslib.animation.AppearAnimationCreator; import com.android.settingslib.animation.AppearAnimationUtils; import com.android.settingslib.animation.DisappearAnimationUtils; -import com.android.systemui.Dependency; import com.android.systemui.R; -import java.util.List; - -public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView, - AppearAnimationCreator<LockPatternView.CellState>, - EmergencyButton.EmergencyButtonCallback { +public class KeyguardPatternView extends KeyguardInputView + implements AppearAnimationCreator<LockPatternView.CellState> { private static final String TAG = "SecurityPatternView"; private static final boolean DEBUG = KeyguardConstants.DEBUG; - // how long before we clear the wrong pattern - private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000; - // how many cells the user has to cross before we poke the wakelock - private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; - // How much we scale up the duration of the disappear animation when the current user is locked public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f; // Extra padding, in pixels, that should eat touch events. private static final int PATTERNS_TOUCH_AREA_EXTENSION = 40; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final AppearAnimationUtils mAppearAnimationUtils; private final DisappearAnimationUtils mDisappearAnimationUtils; private final DisappearAnimationUtils mDisappearAnimationUtilsLocked; @@ -78,11 +55,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit private final Rect mTempRect = new Rect(); private final Rect mLockPatternScreenBounds = new Rect(); - private CountDownTimer mCountdownTimer = null; - private LockPatternUtils mLockPatternUtils; - private AsyncTask<?, ?, ?> mPendingLockCheck; private LockPatternView mLockPatternView; - private KeyguardSecurityCallback mCallback; /** * Keeps track of the last time we poked the wake lock during dispatching of the touch event. @@ -92,26 +65,9 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit */ private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS; - /** - * Useful for clearing out the wrong pattern after a delay - */ - private Runnable mCancelPatternRunnable = new Runnable() { - @Override - public void run() { - mLockPatternView.clearPattern(); - } - }; - @VisibleForTesting KeyguardMessageArea mSecurityMessageDisplay; private View mEcaView; private ViewGroup mContainer; - private int mDisappearYTranslation; - - enum FooterMode { - Normal, - ForgotLockPattern, - VerifyUnlocked - } public KeyguardPatternView(Context context) { this(context, null); @@ -119,7 +75,6 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit public KeyguardPatternView(Context context, AttributeSet attrs) { super(context, attrs); - mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); mAppearAnimationUtils = new AppearAnimationUtils(context, AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */, 2.0f /* delayScale */, AnimationUtils.loadInterpolator( @@ -132,50 +87,16 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit (long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */, 0.6f /* delayScale */, AnimationUtils.loadInterpolator( mContext, android.R.interpolator.fast_out_linear_in)); - mDisappearYTranslation = getResources().getDimensionPixelSize( - R.dimen.disappear_y_translation); - } - - @Override - public void setKeyguardCallback(KeyguardSecurityCallback callback) { - mCallback = callback; - } - - @Override - public void setLockPatternUtils(LockPatternUtils utils) { - mLockPatternUtils = utils; } @Override protected void onFinishInflate() { super.onFinishInflate(); - mLockPatternUtils = mLockPatternUtils == null - ? new LockPatternUtils(mContext) : mLockPatternUtils; mLockPatternView = findViewById(R.id.lockPatternView); - mLockPatternView.setSaveEnabled(false); - mLockPatternView.setOnPatternListener(new UnlockPatternListener()); - mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( - KeyguardUpdateMonitor.getCurrentUser())); - - // vibrate mode will be the same for the life of this screen - mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); mEcaView = findViewById(R.id.keyguard_selector_fade_container); mContainer = findViewById(R.id.container); - - EmergencyButton button = findViewById(R.id.emergency_call_button); - if (button != null) { - button.setCallback(this); - } - - View cancelBtn = findViewById(R.id.cancel_button); - if (cancelBtn != null) { - cancelBtn.setOnClickListener(view -> { - mCallback.reset(); - mCallback.onCancelClicked(); - }); - } } @Override @@ -185,11 +106,6 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit } @Override - public void onEmergencyButtonClickedWhenInCall() { - mCallback.reset(); - } - - @Override public boolean onTouchEvent(MotionEvent ev) { boolean result = super.onTouchEvent(ev); // as long as the user is entering a pattern (i.e sending a touch event that was handled @@ -217,248 +133,11 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit } @Override - public void reset() { - // reset lock pattern - mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( - KeyguardUpdateMonitor.getCurrentUser())); - mLockPatternView.enableInput(); - mLockPatternView.setEnabled(true); - mLockPatternView.clearPattern(); - - if (mSecurityMessageDisplay == null) { - return; - } - - // if the user is currently locked out, enforce it. - long deadline = mLockPatternUtils.getLockoutAttemptDeadline( - KeyguardUpdateMonitor.getCurrentUser()); - if (deadline != 0) { - handleAttemptLockout(deadline); - } else { - displayDefaultSecurityMessage(); - } - } - - private void displayDefaultSecurityMessage() { - if (mSecurityMessageDisplay != null) { - mSecurityMessageDisplay.setMessage(""); - } - } - - @Override - public void showUsabilityHint() { - } - - @Override - public boolean disallowInterceptTouch(MotionEvent event) { + boolean disallowInterceptTouch(MotionEvent event) { return !mLockPatternView.isEmpty() || mLockPatternScreenBounds.contains((int) event.getRawX(), (int) event.getRawY()); } - /** TODO: hook this up */ - public void cleanUp() { - if (DEBUG) Log.v(TAG, "Cleanup() called on " + this); - mLockPatternUtils = null; - mLockPatternView.setOnPatternListener(null); - } - - private class UnlockPatternListener implements LockPatternView.OnPatternListener { - - @Override - public void onPatternStart() { - mLockPatternView.removeCallbacks(mCancelPatternRunnable); - mSecurityMessageDisplay.setMessage(""); - } - - @Override - public void onPatternCleared() { - } - - @Override - public void onPatternCellAdded(List<LockPatternView.Cell> pattern) { - mCallback.userActivity(); - mCallback.onUserInput(); - } - - @Override - public void onPatternDetected(final List<LockPatternView.Cell> pattern) { - mKeyguardUpdateMonitor.setCredentialAttempted(); - mLockPatternView.disableInput(); - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - } - - final int userId = KeyguardUpdateMonitor.getCurrentUser(); - if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { - mLockPatternView.enableInput(); - onPatternChecked(userId, false, 0, false /* not valid - too short */); - return; - } - - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL); - LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); - } - mPendingLockCheck = LockPatternChecker.checkCredential( - mLockPatternUtils, - LockscreenCredential.createPattern(pattern), - userId, - new LockPatternChecker.OnCheckCallback() { - - @Override - public void onEarlyMatched() { - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionEnd( - ACTION_CHECK_CREDENTIAL); - } - onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */, - true /* isValidPattern */); - } - - @Override - public void onChecked(boolean matched, int timeoutMs) { - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionEnd( - ACTION_CHECK_CREDENTIAL_UNLOCKED); - } - mLockPatternView.enableInput(); - mPendingLockCheck = null; - if (!matched) { - onPatternChecked(userId, false /* matched */, timeoutMs, - true /* isValidPattern */); - } - } - - @Override - public void onCancelled() { - // We already got dismissed with the early matched callback, so we - // cancelled the check. However, we still need to note down the latency. - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionEnd( - ACTION_CHECK_CREDENTIAL_UNLOCKED); - } - } - }); - if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { - mCallback.userActivity(); - mCallback.onUserInput(); - } - } - - private void onPatternChecked(int userId, boolean matched, int timeoutMs, - boolean isValidPattern) { - boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; - if (matched) { - mCallback.reportUnlockAttempt(userId, true, 0); - if (dismissKeyguard) { - mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); - mCallback.dismiss(true, userId); - } - } else { - mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); - if (isValidPattern) { - mCallback.reportUnlockAttempt(userId, false, timeoutMs); - if (timeoutMs > 0) { - long deadline = mLockPatternUtils.setLockoutAttemptDeadline( - userId, timeoutMs); - handleAttemptLockout(deadline); - } - } - if (timeoutMs == 0) { - mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern); - mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); - } - } - } - } - - private void handleAttemptLockout(long elapsedRealtimeDeadline) { - mLockPatternView.clearPattern(); - mLockPatternView.setEnabled(false); - final long elapsedRealtime = SystemClock.elapsedRealtime(); - final long secondsInFuture = (long) Math.ceil( - (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); - mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { - - @Override - public void onTick(long millisUntilFinished) { - final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); - mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString( - R.plurals.kg_too_many_failed_attempts_countdown, - secondsRemaining, secondsRemaining)); - } - - @Override - public void onFinish() { - mLockPatternView.setEnabled(true); - displayDefaultSecurityMessage(); - } - - }.start(); - } - - @Override - public boolean needsInput() { - return false; - } - - @Override - public void onPause() { - if (mCountdownTimer != null) { - mCountdownTimer.cancel(); - mCountdownTimer = null; - } - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - mPendingLockCheck = null; - } - displayDefaultSecurityMessage(); - } - - @Override - public void onResume(int reason) { - } - - @Override - public KeyguardSecurityCallback getCallback() { - return mCallback; - } - - @Override - public void showPromptReason(int reason) { - switch (reason) { - case PROMPT_REASON_RESTART: - mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern); - break; - case PROMPT_REASON_TIMEOUT: - mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern); - break; - case PROMPT_REASON_DEVICE_ADMIN: - mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_device_admin); - break; - case PROMPT_REASON_USER_REQUEST: - mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request); - break; - case PROMPT_REASON_PREPARE_FOR_UPDATE: - mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern); - break; - case PROMPT_REASON_NONE: - break; - default: - mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern); - break; - } - } - - @Override - public void showMessage(CharSequence message, ColorStateList colorState) { - if (colorState != null) { - mSecurityMessageDisplay.setNextMessageColor(colorState); - } - mSecurityMessageDisplay.setMessage(message); - } - - @Override public void startAppearAnimation() { enableClipping(false); setAlpha(1f); @@ -467,12 +146,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit 0, mAppearAnimationUtils.getInterpolator()); mAppearAnimationUtils.startAnimation2d( mLockPatternView.getCellStates(), - new Runnable() { - @Override - public void run() { - enableClipping(true); - } - }, + () -> enableClipping(true), this); if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, @@ -484,11 +158,9 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit } } - @Override - public boolean startDisappearAnimation(final Runnable finishRunnable) { - float durationMultiplier = mKeyguardUpdateMonitor.needsSlowUnlockTransition() - ? DISAPPEAR_MULTIPLIER_LOCKED - : 1f; + public boolean startDisappearAnimation(boolean needsSlowUnlockTransition, + final Runnable finishRunnable) { + float durationMultiplier = needsSlowUnlockTransition ? DISAPPEAR_MULTIPLIER_LOCKED : 1f; mLockPatternView.clearPattern(); enableClipping(false); setTranslationY(0); @@ -497,10 +169,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit -mDisappearAnimationUtils.getStartTranslation(), mDisappearAnimationUtils.getInterpolator()); - DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor - .needsSlowUnlockTransition() - ? mDisappearAnimationUtilsLocked - : mDisappearAnimationUtils; + DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition + ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils; disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(), () -> { enableClipping(true); @@ -549,7 +219,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit @Override public CharSequence getTitle() { - return getContext().getString( + return getResources().getString( com.android.internal.R.string.keyguard_accessibility_pattern_unlock); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java new file mode 100644 index 000000000000..3db9db7be00c --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -0,0 +1,349 @@ +/* + * 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.keyguard; + +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; + +import android.content.res.ColorStateList; +import android.os.AsyncTask; +import android.os.CountDownTimer; +import android.os.SystemClock; +import android.view.View; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternChecker; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockPatternView; +import com.android.internal.widget.LockPatternView.Cell; +import com.android.internal.widget.LockscreenCredential; +import com.android.keyguard.EmergencyButton.EmergencyButtonCallback; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; + +import java.util.List; + +public class KeyguardPatternViewController + extends KeyguardInputViewController<KeyguardPatternView> { + + // how many cells the user has to cross before we poke the wakelock + private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; + + // how long before we clear the wrong pattern + private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; + + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final LockPatternUtils mLockPatternUtils; + private final LatencyTracker mLatencyTracker; + private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory; + + private KeyguardMessageAreaController mMessageAreaController; + private LockPatternView mLockPatternView; + private CountDownTimer mCountdownTimer; + private AsyncTask<?, ?, ?> mPendingLockCheck; + + private EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() { + @Override + public void onEmergencyButtonClickedWhenInCall() { + getKeyguardSecurityCallback().reset(); + } + }; + + /** + * Useful for clearing out the wrong pattern after a delay + */ + private Runnable mCancelPatternRunnable = new Runnable() { + @Override + public void run() { + mLockPatternView.clearPattern(); + } + }; + + private class UnlockPatternListener implements LockPatternView.OnPatternListener { + + @Override + public void onPatternStart() { + mLockPatternView.removeCallbacks(mCancelPatternRunnable); + mMessageAreaController.setMessage(""); + } + + @Override + public void onPatternCleared() { + } + + @Override + public void onPatternCellAdded(List<Cell> pattern) { + getKeyguardSecurityCallback().userActivity(); + getKeyguardSecurityCallback().onUserInput(); + } + + @Override + public void onPatternDetected(final List<LockPatternView.Cell> pattern) { + mKeyguardUpdateMonitor.setCredentialAttempted(); + mLockPatternView.disableInput(); + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + } + + final int userId = KeyguardUpdateMonitor.getCurrentUser(); + if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { + mLockPatternView.enableInput(); + onPatternChecked(userId, false, 0, false /* not valid - too short */); + return; + } + + mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL); + mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); + mPendingLockCheck = LockPatternChecker.checkCredential( + mLockPatternUtils, + LockscreenCredential.createPattern(pattern), + userId, + new LockPatternChecker.OnCheckCallback() { + + @Override + public void onEarlyMatched() { + mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL); + onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */, + true /* isValidPattern */); + } + + @Override + public void onChecked(boolean matched, int timeoutMs) { + mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED); + mLockPatternView.enableInput(); + mPendingLockCheck = null; + if (!matched) { + onPatternChecked(userId, false /* matched */, timeoutMs, + true /* isValidPattern */); + } + } + + @Override + public void onCancelled() { + // We already got dismissed with the early matched callback, so we + // cancelled the check. However, we still need to note down the latency. + mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED); + } + }); + if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { + getKeyguardSecurityCallback().userActivity(); + getKeyguardSecurityCallback().onUserInput(); + } + } + + private void onPatternChecked(int userId, boolean matched, int timeoutMs, + boolean isValidPattern) { + boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; + if (matched) { + getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0); + if (dismissKeyguard) { + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); + getKeyguardSecurityCallback().dismiss(true, userId); + } + } else { + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); + if (isValidPattern) { + getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs); + if (timeoutMs > 0) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline( + userId, timeoutMs); + handleAttemptLockout(deadline); + } + } + if (timeoutMs == 0) { + mMessageAreaController.setMessage(R.string.kg_wrong_pattern); + mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); + } + } + } + } + + protected KeyguardPatternViewController(KeyguardPatternView view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, + LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + LatencyTracker latencyTracker, + KeyguardMessageAreaController.Factory messageAreaControllerFactory) { + super(view, securityMode, keyguardSecurityCallback); + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mLockPatternUtils = lockPatternUtils; + mLatencyTracker = latencyTracker; + mMessageAreaControllerFactory = messageAreaControllerFactory; + KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView); + mMessageAreaController = mMessageAreaControllerFactory.create(kma); + mLockPatternView = mView.findViewById(R.id.lockPatternView); + } + + @Override + public void init() { + super.init(); + mMessageAreaController.init(); + } + + @Override + protected void onViewAttached() { + mLockPatternView.setOnPatternListener(new UnlockPatternListener()); + mLockPatternView.setSaveEnabled(false); + mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( + KeyguardUpdateMonitor.getCurrentUser())); + // vibrate mode will be the same for the life of this screen + mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); + + EmergencyButton button = mView.findViewById(R.id.emergency_call_button); + if (button != null) { + button.setCallback(mEmergencyButtonCallback); + } + + View cancelBtn = mView.findViewById(R.id.cancel_button); + if (cancelBtn != null) { + cancelBtn.setOnClickListener(view -> { + getKeyguardSecurityCallback().reset(); + getKeyguardSecurityCallback().onCancelClicked(); + }); + } + } + + @Override + protected void onViewDetached() { + super.onViewDetached(); + mLockPatternView.setOnPatternListener(null); + EmergencyButton button = mView.findViewById(R.id.emergency_call_button); + if (button != null) { + button.setCallback(null); + } + View cancelBtn = mView.findViewById(R.id.cancel_button); + if (cancelBtn != null) { + cancelBtn.setOnClickListener(null); + } + } + + @Override + public void reset() { + // reset lock pattern + mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( + KeyguardUpdateMonitor.getCurrentUser())); + mLockPatternView.enableInput(); + mLockPatternView.setEnabled(true); + mLockPatternView.clearPattern(); + + // if the user is currently locked out, enforce it. + long deadline = mLockPatternUtils.getLockoutAttemptDeadline( + KeyguardUpdateMonitor.getCurrentUser()); + if (deadline != 0) { + handleAttemptLockout(deadline); + } else { + displayDefaultSecurityMessage(); + } + } + + @Override + public void onPause() { + super.onPause(); + + if (mCountdownTimer != null) { + mCountdownTimer.cancel(); + mCountdownTimer = null; + } + + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + mPendingLockCheck = null; + } + displayDefaultSecurityMessage(); + } + + @Override + public boolean needsInput() { + return false; + } + + @Override + public void showPromptReason(int reason) { + /// TODO: move all this logic into the MessageAreaController? + switch (reason) { + case PROMPT_REASON_RESTART: + mMessageAreaController.setMessage(R.string.kg_prompt_reason_restart_pattern); + break; + case PROMPT_REASON_TIMEOUT: + mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); + break; + case PROMPT_REASON_DEVICE_ADMIN: + mMessageAreaController.setMessage(R.string.kg_prompt_reason_device_admin); + break; + case PROMPT_REASON_USER_REQUEST: + mMessageAreaController.setMessage(R.string.kg_prompt_reason_user_request); + break; + case PROMPT_REASON_PREPARE_FOR_UPDATE: + mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); + break; + case PROMPT_REASON_NONE: + break; + default: + mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); + break; + } + } + + @Override + public void showMessage(CharSequence message, ColorStateList colorState) { + if (colorState != null) { + mMessageAreaController.setNextMessageColor(colorState); + } + mMessageAreaController.setMessage(message); + } + + @Override + public void startAppearAnimation() { + super.startAppearAnimation(); + } + + @Override + public boolean startDisappearAnimation(Runnable finishRunnable) { + return mView.startDisappearAnimation( + mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); + } + + private void displayDefaultSecurityMessage() { + mMessageAreaController.setMessage(""); + } + + private void handleAttemptLockout(long elapsedRealtimeDeadline) { + mLockPatternView.clearPattern(); + mLockPatternView.setEnabled(false); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long secondsInFuture = (long) Math.ceil( + (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); + mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { + + @Override + public void onTick(long millisUntilFinished) { + final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); + mMessageAreaController.setMessage(mView.getResources().getQuantityString( + R.plurals.kg_too_many_failed_attempts_countdown, + secondsRemaining, secondsRemaining)); + } + + @Override + public void onFinish() { + mLockPatternView.setEnabled(true); + displayDefaultSecurityMessage(); + } + + }.start(); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index c7f27cf8a71a..7fa43116a7b1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -16,11 +16,17 @@ package com.android.keyguard; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; + import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.KeyEvent; -import android.view.MotionEvent; import android.view.View; import com.android.internal.widget.LockscreenCredential; @@ -29,22 +35,12 @@ import com.android.systemui.R; /** * A Pin based Keyguard input view */ -public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView - implements View.OnKeyListener, View.OnTouchListener { +public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView { protected PasswordTextView mPasswordEntry; private View mOkButton; private View mDeleteButton; - private View mButton0; - private View mButton1; - private View mButton2; - private View mButton3; - private View mButton4; - private View mButton5; - private View mButton6; - private View mButton7; - private View mButton8; - private View mButton9; + private View[] mButtons = new View[10]; public KeyguardPinBasedInputView(Context context) { this(context, null); @@ -62,7 +58,6 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView @Override protected void resetState() { - setPasswordEntryEnabled(true); } @Override @@ -86,10 +81,10 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (KeyEvent.isConfirmKey(keyCode)) { - performClick(mOkButton); + mOkButton.performClick(); return true; } else if (keyCode == KeyEvent.KEYCODE_DEL) { - performClick(mDeleteButton); + mDeleteButton.performClick(); return true; } if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) { @@ -125,42 +120,9 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView } } - private void performClick(View view) { - view.performClick(); - } - private void performNumberClick(int number) { - switch (number) { - case 0: - performClick(mButton0); - break; - case 1: - performClick(mButton1); - break; - case 2: - performClick(mButton2); - break; - case 3: - performClick(mButton3); - break; - case 4: - performClick(mButton4); - break; - case 5: - performClick(mButton5); - break; - case 6: - performClick(mButton6); - break; - case 7: - performClick(mButton7); - break; - case 8: - performClick(mButton8); - break; - case 9: - performClick(mButton9); - break; + if (number >= 0 && number <= 9) { + mButtons[number].performClick(); } } @@ -177,94 +139,31 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView @Override protected void onFinishInflate() { mPasswordEntry = findViewById(getPasswordTextViewId()); - mPasswordEntry.setOnKeyListener(this); // Set selected property on so the view can send accessibility events. mPasswordEntry.setSelected(true); - mPasswordEntry.setUserActivityListener(new PasswordTextView.UserActivityListener() { - @Override - public void onUserActivity() { - onUserInput(); - } - }); - mOkButton = findViewById(R.id.key_enter); - if (mOkButton != null) { - mOkButton.setOnTouchListener(this); - mOkButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mPasswordEntry.isEnabled()) { - verifyPasswordAndUnlock(); - } - } - }); - mOkButton.setOnHoverListener(new LiftToActivateListener(getContext())); - } mDeleteButton = findViewById(R.id.delete_button); mDeleteButton.setVisibility(View.VISIBLE); - mDeleteButton.setOnTouchListener(this); - mDeleteButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - // check for time-based lockouts - if (mPasswordEntry.isEnabled()) { - mPasswordEntry.deleteLastChar(); - } - } - }); - mDeleteButton.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - // check for time-based lockouts - if (mPasswordEntry.isEnabled()) { - resetPasswordText(true /* animate */, true /* announce */); - } - doHapticKeyClick(); - return true; - } - }); - mButton0 = findViewById(R.id.key0); - mButton1 = findViewById(R.id.key1); - mButton2 = findViewById(R.id.key2); - mButton3 = findViewById(R.id.key3); - mButton4 = findViewById(R.id.key4); - mButton5 = findViewById(R.id.key5); - mButton6 = findViewById(R.id.key6); - mButton7 = findViewById(R.id.key7); - mButton8 = findViewById(R.id.key8); - mButton9 = findViewById(R.id.key9); + mButtons[0] = findViewById(R.id.key0); + mButtons[1] = findViewById(R.id.key1); + mButtons[2] = findViewById(R.id.key2); + mButtons[3] = findViewById(R.id.key3); + mButtons[4] = findViewById(R.id.key4); + mButtons[5] = findViewById(R.id.key5); + mButtons[6] = findViewById(R.id.key6); + mButtons[7] = findViewById(R.id.key7); + mButtons[8] = findViewById(R.id.key8); + mButtons[9] = findViewById(R.id.key9); mPasswordEntry.requestFocus(); super.onFinishInflate(); } @Override - public void onResume(int reason) { - super.onResume(reason); - mPasswordEntry.requestFocus(); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - doHapticKeyClick(); - } - return false; - } - - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - return onKeyDown(keyCode, event); - } - return false; - } - - @Override public CharSequence getTitle() { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_pin_unlock); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java new file mode 100644 index 000000000000..4d0ebfffbe04 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.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.keyguard; + +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnKeyListener; +import android.view.View.OnTouchListener; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; + +public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView> + extends KeyguardAbsKeyInputViewController<T> { + + private final LiftToActivateListener mLiftToActivateListener; + protected PasswordTextView mPasswordEntry; + + private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + return mView.onKeyDown(keyCode, event); + } + return false; + }; + + private final OnTouchListener mOnTouchListener = (v, event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mView.doHapticKeyClick(); + } + return false; + }; + + protected KeyguardPinBasedInputViewController(T view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, + LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + LatencyTracker latencyTracker, + LiftToActivateListener liftToActivateListener) { + super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, + messageAreaControllerFactory, latencyTracker); + mLiftToActivateListener = liftToActivateListener; + mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); + } + + @Override + protected void onViewAttached() { + super.onViewAttached(); + + mPasswordEntry.setOnKeyListener(mOnKeyListener); + mPasswordEntry.setUserActivityListener(this::onUserInput); + + View deleteButton = mView.findViewById(R.id.delete_button); + deleteButton.setOnTouchListener(mOnTouchListener); + deleteButton.setOnClickListener(v -> { + // check for time-based lockouts + if (mPasswordEntry.isEnabled()) { + mPasswordEntry.deleteLastChar(); + } + }); + deleteButton.setOnLongClickListener(v -> { + // check for time-based lockouts + if (mPasswordEntry.isEnabled()) { + mView.resetPasswordText(true /* animate */, true /* announce */); + } + mView.doHapticKeyClick(); + return true; + }); + + View okButton = mView.findViewById(R.id.key_enter); + if (okButton != null) { + okButton.setOnTouchListener(mOnTouchListener); + okButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mPasswordEntry.isEnabled()) { + verifyPasswordAndUnlock(); + } + } + }); + okButton.setOnHoverListener(mLiftToActivateListener); + } + } + + @Override + public void onResume(int reason) { + super.onResume(reason); + mPasswordEntry.requestFocus(); + } + + @Override + void resetState() { + mView.setPasswordEntryEnabled(true); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java new file mode 100644 index 000000000000..6769436be8ef --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard; + +import android.view.View; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; + +public class KeyguardPinViewController + extends KeyguardPinBasedInputViewController<KeyguardPINView> { + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + + protected KeyguardPinViewController(KeyguardPINView view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + LatencyTracker latencyTracker, + LiftToActivateListener liftToActivateListener) { + super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, + messageAreaControllerFactory, latencyTracker, liftToActivateListener); + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + } + + @Override + protected void onViewAttached() { + super.onViewAttached(); + + View cancelBtn = mView.findViewById(R.id.cancel_button); + if (cancelBtn != null) { + cancelBtn.setOnClickListener(view -> { + getKeyguardSecurityCallback().reset(); + getKeyguardSecurityCallback().onCancelClicked(); + }); + } + } + + @Override + void resetState() { + super.resetState(); + mMessageAreaController.setMessage(""); + } + + @Override + public boolean startDisappearAnimation(Runnable finishRunnable) { + return mView.startDisappearAnimation( + mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 81d37a830f8f..b62ea6bc2ff6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -19,8 +19,6 @@ import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; -import static com.android.systemui.DejankUtils.whitelistIpcs; - import static java.lang.Integer.max; import android.animation.Animator; @@ -28,25 +26,14 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.Activity; import android.app.AlertDialog; -import android.app.admin.DevicePolicyManager; import android.content.Context; -import android.content.Intent; -import android.content.res.ColorStateList; import android.graphics.Insets; import android.graphics.Rect; -import android.metrics.LogMaker; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; import android.util.AttributeSet; -import android.util.Log; import android.util.MathUtils; -import android.util.Slog; import android.util.TypedValue; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; -import android.view.View; import android.view.ViewConfiguration; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; @@ -61,42 +48,30 @@ import androidx.annotation.VisibleForTesting; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.SpringAnimation; -import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.UiEventLoggerImpl; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.settingslib.utils.ThreadUtils; -import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.SystemUIFactory; -import com.android.systemui.shared.system.SysUiStatsLog; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.InjectionInflationController; import java.util.List; -public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView { - private static final boolean DEBUG = KeyguardConstants.DEBUG; - private static final String TAG = "KeyguardSecurityView"; - - private static final int USER_TYPE_PRIMARY = 1; - private static final int USER_TYPE_WORK_PROFILE = 2; - private static final int USER_TYPE_SECONDARY_USER = 3; +public class KeyguardSecurityContainer extends FrameLayout { + static final int USER_TYPE_PRIMARY = 1; + static final int USER_TYPE_WORK_PROFILE = 2; + static final int USER_TYPE_SECONDARY_USER = 3; // Bouncer is dismissed due to no security. - private static final int BOUNCER_DISMISS_NONE_SECURITY = 0; + static final int BOUNCER_DISMISS_NONE_SECURITY = 0; // Bouncer is dismissed due to pin, password or pattern entered. - private static final int BOUNCER_DISMISS_PASSWORD = 1; + static final int BOUNCER_DISMISS_PASSWORD = 1; // Bouncer is dismissed due to biometric (face, fingerprint or iris) authenticated. - private static final int BOUNCER_DISMISS_BIOMETRIC = 2; + static final int BOUNCER_DISMISS_BIOMETRIC = 2; // Bouncer is dismissed due to extended access granted. - private static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3; + static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3; // Bouncer is dismissed due to sim card unlock code entered. - private static final int BOUNCER_DISMISS_SIM = 4; + static final int BOUNCER_DISMISS_SIM = 4; // Make the view move slower than the finger, as if the spring were applying force. private static final float TOUCH_Y_MULTIPLIER = 0.25f; @@ -105,36 +80,23 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe // How much to scale the default slop by, to avoid accidental drags. private static final float SLOP_SCALE = 4f; - private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl(); - private static final long IME_DISAPPEAR_DURATION_MS = 125; - private KeyguardSecurityModel mSecurityModel; - private LockPatternUtils mLockPatternUtils; - @VisibleForTesting KeyguardSecurityViewFlipper mSecurityViewFlipper; - private boolean mIsVerifyUnlockOnly; - private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid; - private KeyguardSecurityView mCurrentSecurityView; - private SecurityCallback mSecurityCallback; private AlertDialog mAlertDialog; - private InjectionInflationController mInjectionInflationController; private boolean mSwipeUpToRetry; - private AdminSecondaryLockScreenController mSecondaryLockScreenController; private final ViewConfiguration mViewConfiguration; private final SpringAnimation mSpringAnimation; private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); - private final KeyguardUpdateMonitor mUpdateMonitor; - private final KeyguardStateController mKeyguardStateController; - private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private float mLastTouchY = -1; private int mActivePointerId = -1; private boolean mIsDragging; private float mStartTouchY = -1; private boolean mDisappearAnimRunning; + private SwipeListener mSwipeListener; private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { @@ -186,19 +148,22 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe // Used to notify the container when something interesting happens. public interface SecurityCallback { - public boolean dismiss(boolean authenticated, int targetUserId, - boolean bypassSecondaryLockScreen); - public void userActivity(); - public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput); + boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen); + void userActivity(); + void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput); /** * @param strongAuth wheher the user has authenticated with strong authentication like * pattern, password or PIN but not by trust agents or fingerprint * @param targetUserId a user that needs to be the foreground user at the finish completion. */ - public void finish(boolean strongAuth, int targetUserId); - public void reset(); - public void onCancelClicked(); + void finish(boolean strongAuth, int targetUserId); + void reset(); + void onCancelClicked(); + } + + public interface SwipeListener { + void onSwipeUp(); } @VisibleForTesting @@ -249,52 +214,24 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mSecurityModel = Dependency.get(KeyguardSecurityModel.class); - mLockPatternUtils = new LockPatternUtils(context); - mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y); - mInjectionInflationController = new InjectionInflationController( - SystemUIFactory.getInstance().getSysUIComponent().createViewInstanceCreatorFactory()); mViewConfiguration = ViewConfiguration.get(context); - mKeyguardStateController = Dependency.get(KeyguardStateController.class); - mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this, - mUpdateMonitor, mCallback, new Handler(Looper.myLooper())); - } - - public void setSecurityCallback(SecurityCallback callback) { - mSecurityCallback = callback; } - @Override - public void onResume(int reason) { - if (mCurrentSecuritySelection != SecurityMode.None) { - getSecurityView(mCurrentSecuritySelection).onResume(reason); - } + void onResume(SecurityMode securityMode, boolean faceAuthEnabled) { mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback); - updateBiometricRetry(); + updateBiometricRetry(securityMode, faceAuthEnabled); } - @Override public void onPause() { if (mAlertDialog != null) { mAlertDialog.dismiss(); mAlertDialog = null; } - mSecondaryLockScreenController.hide(); - if (mCurrentSecuritySelection != SecurityMode.None) { - getSecurityView(mCurrentSecuritySelection).onPause(); - } mSecurityViewFlipper.setWindowInsetsAnimationCallback(null); } @Override - public void onStartingToHide() { - if (mCurrentSecuritySelection != SecurityMode.None) { - getSecurityView(mCurrentSecuritySelection).onStartingToHide(); - } - } - - @Override public boolean shouldDelayChildPressedState() { return true; } @@ -316,13 +253,12 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe return false; } // Avoid dragging the pattern view - if (mCurrentSecurityView.disallowInterceptTouch(event)) { + if (mSecurityViewFlipper.getSecurityView().disallowInterceptTouch(event)) { return false; } int index = event.findPointerIndex(mActivePointerId); float touchSlop = mViewConfiguration.getScaledTouchSlop() * SLOP_SCALE; - if (mCurrentSecurityView != null && index != -1 - && mStartTouchY - event.getY(index) > touchSlop) { + if (index != -1 && mStartTouchY - event.getY(index) > touchSlop) { mIsDragging = true; return true; } @@ -370,31 +306,28 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe } if (action == MotionEvent.ACTION_UP) { if (-getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - MIN_DRAG_SIZE, getResources().getDisplayMetrics()) - && !mUpdateMonitor.isFaceDetectionRunning()) { - mUpdateMonitor.requestFaceAuth(); - mCallback.userActivity(); - showMessage(null, null); + MIN_DRAG_SIZE, getResources().getDisplayMetrics())) { + if (mSwipeListener != null) { + mSwipeListener.onSwipeUp(); + } } } return true; } + void setSwipeListener(SwipeListener swipeListener) { + mSwipeListener = swipeListener; + } + private void startSpringAnimation(float startVelocity) { mSpringAnimation .setStartVelocity(startVelocity) .animateToFinalPosition(0); } - public void startAppearAnimation() { - if (mCurrentSecuritySelection != SecurityMode.None) { - getSecurityView(mCurrentSecuritySelection).startAppearAnimation(); - } - } - - public boolean startDisappearAnimation(Runnable onFinishRunnable) { + public void startDisappearAnimation(SecurityMode securitySelection) { mDisappearAnimRunning = true; - if (mCurrentSecuritySelection == SecurityMode.Password) { + if (securitySelection == SecurityMode.Password) { mSecurityViewFlipper.getWindowInsetsController().controlWindowInsetsAnimation(ime(), IME_DISAPPEAR_DURATION_MS, Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() { @@ -439,19 +372,13 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe } }); } - if (mCurrentSecuritySelection != SecurityMode.None) { - return getSecurityView(mCurrentSecuritySelection).startDisappearAnimation( - onFinishRunnable); - } - return false; } /** * Enables/disables swipe up to retry on the bouncer. */ - private void updateBiometricRetry() { - SecurityMode securityMode = getSecurityMode(); - mSwipeUpToRetry = mKeyguardStateController.isFaceAuthEnabled() + private void updateBiometricRetry(SecurityMode securityMode, boolean faceAuthEnabled) { + mSwipeUpToRetry = faceAuthEnabled && securityMode != SecurityMode.SimPin && securityMode != SecurityMode.SimPuk && securityMode != SecurityMode.None; @@ -461,53 +388,11 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe return mSecurityViewFlipper.getTitle(); } - @VisibleForTesting - protected KeyguardSecurityView getSecurityView(SecurityMode securityMode) { - final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); - KeyguardSecurityView view = null; - final int children = mSecurityViewFlipper.getChildCount(); - for (int child = 0; child < children; child++) { - if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) { - view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child)); - break; - } - } - int layoutId = getLayoutIdFor(securityMode); - if (view == null && layoutId != 0) { - final LayoutInflater inflater = LayoutInflater.from(mContext); - if (DEBUG) Log.v(TAG, "inflating id = " + layoutId); - View v = mInjectionInflationController.injectable(inflater) - .inflate(layoutId, mSecurityViewFlipper, false); - mSecurityViewFlipper.addView(v); - updateSecurityView(v); - view = (KeyguardSecurityView)v; - view.reset(); - } - - return view; - } - - private void updateSecurityView(View view) { - if (view instanceof KeyguardSecurityView) { - KeyguardSecurityView ksv = (KeyguardSecurityView) view; - ksv.setKeyguardCallback(mCallback); - ksv.setLockPatternUtils(mLockPatternUtils); - } else { - Log.w(TAG, "View " + view + " is not a KeyguardSecurityView"); - } - } @Override public void onFinishInflate() { super.onFinishInflate(); mSecurityViewFlipper = findViewById(R.id.view_flipper); - mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils); - } - - public void setLockPatternUtils(LockPatternUtils utils) { - mLockPatternUtils = utils; - mSecurityModel.setLockPatternUtils(utils); - mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils); } @Override @@ -539,11 +424,12 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe mAlertDialog.show(); } - private void showTimeoutDialog(int userId, int timeoutMs) { - int timeoutInSeconds = (int) timeoutMs / 1000; + void showTimeoutDialog(int userId, int timeoutMs, LockPatternUtils lockPatternUtils, + SecurityMode securityMode) { + int timeoutInSeconds = timeoutMs / 1000; int messageId = 0; - switch (mSecurityModel.getSecurityMode(userId)) { + switch (securityMode) { case Pattern: messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message; break; @@ -563,13 +449,13 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe if (messageId != 0) { final String message = mContext.getString(messageId, - mLockPatternUtils.getCurrentFailedPasswordAttempts(userId), + lockPatternUtils.getCurrentFailedPasswordAttempts(userId), timeoutInSeconds); showDialog(null, message); } } - private void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { + void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { String message = null; switch (userType) { case USER_TYPE_PRIMARY: @@ -588,7 +474,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe showDialog(null, message); } - private void showWipeDialog(int attempts, int userType) { + void showWipeDialog(int attempts, int userType) { String message = null; switch (userType) { case USER_TYPE_PRIMARY: @@ -607,358 +493,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe showDialog(null, message); } - private void reportFailedUnlockAttempt(int userId, int timeoutMs) { - // +1 for this time - final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1; - - if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts); - - final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager(); - final int failedAttemptsBeforeWipe = - dpm.getMaximumFailedPasswordsForWipe(null, userId); - - final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ? - (failedAttemptsBeforeWipe - failedAttempts) - : Integer.MAX_VALUE; // because DPM returns 0 if no restriction - if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) { - // The user has installed a DevicePolicyManager that requests a user/profile to be wiped - // N attempts. Once we get below the grace period, we post this dialog every time as a - // clear warning until the deletion fires. - // Check which profile has the strictest policy for failed password attempts - final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId); - int userType = USER_TYPE_PRIMARY; - if (expiringUser == userId) { - // TODO: http://b/23522538 - if (expiringUser != UserHandle.USER_SYSTEM) { - userType = USER_TYPE_SECONDARY_USER; - } - } else if (expiringUser != UserHandle.USER_NULL) { - userType = USER_TYPE_WORK_PROFILE; - } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY - if (remainingBeforeWipe > 0) { - showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType); - } else { - // Too many attempts. The device will be wiped shortly. - Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!"); - showWipeDialog(failedAttempts, userType); - } - } - mLockPatternUtils.reportFailedPasswordAttempt(userId); - if (timeoutMs > 0) { - mLockPatternUtils.reportPasswordLockout(timeoutMs, userId); - showTimeoutDialog(userId, timeoutMs); - } - } - - /** - * Shows the primary security screen for the user. This will be either the multi-selector - * or the user's security method. - * @param turningOff true if the device is being turned off - */ - void showPrimarySecurityScreen(boolean turningOff) { - SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode( - KeyguardUpdateMonitor.getCurrentUser())); - if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")"); - showSecurityScreen(securityMode); - } - - /** - * Shows the next security screen if there is one. - * @param authenticated true if the user entered the correct authentication - * @param targetUserId a user that needs to be the foreground user at the finish (if called) - * completion. - * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary - * secondary lock screen requirement, if any. - * @return true if keyguard is done - */ - boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId, - boolean bypassSecondaryLockScreen) { - if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")"); - boolean finish = false; - boolean strongAuth = false; - int eventSubtype = -1; - BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN; - if (mUpdateMonitor.getUserHasTrust(targetUserId)) { - finish = true; - eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS; - uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS; - } else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) { - finish = true; - eventSubtype = BOUNCER_DISMISS_BIOMETRIC; - uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC; - } else if (SecurityMode.None == mCurrentSecuritySelection) { - SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); - if (SecurityMode.None == securityMode) { - finish = true; // no security required - eventSubtype = BOUNCER_DISMISS_NONE_SECURITY; - uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY; - } else { - showSecurityScreen(securityMode); // switch to the alternate security view - } - } else if (authenticated) { - switch (mCurrentSecuritySelection) { - case Pattern: - case Password: - case PIN: - strongAuth = true; - finish = true; - eventSubtype = BOUNCER_DISMISS_PASSWORD; - uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD; - break; - - case SimPin: - case SimPuk: - // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home - SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); - if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled( - KeyguardUpdateMonitor.getCurrentUser())) { - finish = true; - eventSubtype = BOUNCER_DISMISS_SIM; - uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM; - } else { - showSecurityScreen(securityMode); - } - break; - - default: - Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe"); - showPrimarySecurityScreen(false); - break; - } - } - // Check for device admin specified additional security measures. - if (finish && !bypassSecondaryLockScreen) { - Intent secondaryLockscreenIntent = - mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId); - if (secondaryLockscreenIntent != null) { - mSecondaryLockScreenController.show(secondaryLockscreenIntent); - return false; - } - } - if (eventSubtype != -1) { - mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) - .setType(MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype)); - } - if (uiEvent != BouncerUiEvent.UNKNOWN) { - sUiEventLogger.log(uiEvent); - } - if (finish) { - mSecurityCallback.finish(strongAuth, targetUserId); - } - return finish; - } - - /** - * Switches to the given security view unless it's already being shown, in which case - * this is a no-op. - * - * @param securityMode - */ - private void showSecurityScreen(SecurityMode securityMode) { - if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")"); - - if (securityMode == mCurrentSecuritySelection) return; - - KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection); - KeyguardSecurityView newView = getSecurityView(securityMode); - - // Emulate Activity life cycle - if (oldView != null) { - oldView.onPause(); - oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view - } - if (securityMode != SecurityMode.None) { - newView.onResume(KeyguardSecurityView.VIEW_REVEALED); - newView.setKeyguardCallback(mCallback); - } - - // Find and show this child. - final int childCount = mSecurityViewFlipper.getChildCount(); - - final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); - for (int i = 0; i < childCount; i++) { - if (mSecurityViewFlipper.getChildAt(i).getId() == securityViewIdForMode) { - mSecurityViewFlipper.setDisplayedChild(i); - break; - } - } - - mCurrentSecuritySelection = securityMode; - mCurrentSecurityView = newView; - mSecurityCallback.onSecurityModeChanged(securityMode, - securityMode != SecurityMode.None && newView.needsInput()); - } - - private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() { - public void userActivity() { - if (mSecurityCallback != null) { - mSecurityCallback.userActivity(); - } - } - - @Override - public void onUserInput() { - mUpdateMonitor.cancelFaceAuth(); - } - - @Override - public void dismiss(boolean authenticated, int targetId) { - dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false); - } - - @Override - public void dismiss(boolean authenticated, int targetId, - boolean bypassSecondaryLockScreen) { - mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen); - } - - public boolean isVerifyUnlockOnly() { - return mIsVerifyUnlockOnly; - } - - public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { - if (success) { - SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, - SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS); - mLockPatternUtils.reportSuccessfulPasswordAttempt(userId); - // Force a garbage collection in an attempt to erase any lockscreen password left in - // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard - // dismiss animation janky. - ThreadUtils.postOnBackgroundThread(() -> { - try { - Thread.sleep(5000); - } catch (InterruptedException ignored) { } - Runtime.getRuntime().gc(); - }); - } else { - SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, - SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE); - KeyguardSecurityContainer.this.reportFailedUnlockAttempt(userId, timeoutMs); - } - mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) - .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE)); - sUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS - : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE); - } - - public void reset() { - mSecurityCallback.reset(); - } - - public void onCancelClicked() { - mSecurityCallback.onCancelClicked(); - } - }; - - // The following is used to ignore callbacks from SecurityViews that are no longer current - // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the - // state for the current security method. - private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() { - @Override - public void userActivity() { } - @Override - public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { } - @Override - public boolean isVerifyUnlockOnly() { return false; } - @Override - public void dismiss(boolean securityVerified, int targetUserId) { } - @Override - public void dismiss(boolean authenticated, int targetId, - boolean bypassSecondaryLockScreen) { } - @Override - public void onUserInput() { } - @Override - public void reset() {} - }; - - private int getSecurityViewIdForMode(SecurityMode securityMode) { - switch (securityMode) { - case Pattern: return R.id.keyguard_pattern_view; - case PIN: return R.id.keyguard_pin_view; - case Password: return R.id.keyguard_password_view; - case SimPin: return R.id.keyguard_sim_pin_view; - case SimPuk: return R.id.keyguard_sim_puk_view; - } - return 0; - } - - @VisibleForTesting - public int getLayoutIdFor(SecurityMode securityMode) { - switch (securityMode) { - case Pattern: return R.layout.keyguard_pattern_view; - case PIN: return R.layout.keyguard_pin_view; - case Password: return R.layout.keyguard_password_view; - case SimPin: return R.layout.keyguard_sim_pin_view; - case SimPuk: return R.layout.keyguard_sim_puk_view; - default: - return 0; - } - } - - public SecurityMode getSecurityMode() { - return mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()); - } - - public SecurityMode getCurrentSecurityMode() { - return mCurrentSecuritySelection; - } - - public KeyguardSecurityView getCurrentSecurityView() { - return mCurrentSecurityView; - } - - public void verifyUnlock() { - mIsVerifyUnlockOnly = true; - showSecurityScreen(getSecurityMode()); - } - - public SecurityMode getCurrentSecuritySelection() { - return mCurrentSecuritySelection; - } - - public void dismiss(boolean authenticated, int targetUserId) { - mCallback.dismiss(authenticated, targetUserId); - } - - public boolean needsInput() { - return mSecurityViewFlipper.needsInput(); - } - - @Override - public void setKeyguardCallback(KeyguardSecurityCallback callback) { - mSecurityViewFlipper.setKeyguardCallback(callback); - } - - @Override public void reset() { - mSecurityViewFlipper.reset(); mDisappearAnimRunning = false; } - - @Override - public KeyguardSecurityCallback getCallback() { - return mSecurityViewFlipper.getCallback(); - } - - @Override - public void showPromptReason(int reason) { - if (mCurrentSecuritySelection != SecurityMode.None) { - if (reason != PROMPT_REASON_NONE) { - Log.i(TAG, "Strong auth required, reason: " + reason); - } - getSecurityView(mCurrentSecuritySelection).showPromptReason(reason); - } - } - - public void showMessage(CharSequence message, ColorStateList colorState) { - if (mCurrentSecuritySelection != SecurityMode.None) { - getSecurityView(mCurrentSecuritySelection).showMessage(message, colorState); - } - } - - @Override - public void showUsabilityHint() { - mSecurityViewFlipper.showUsabilityHint(); - } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 17f25bd08ef4..1c23605a8516 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -16,33 +16,167 @@ package com.android.keyguard; +import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC; +import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS; +import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY; +import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD; +import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM; +import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY; +import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER; +import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE; +import static com.android.systemui.DejankUtils.whitelistIpcs; + +import android.app.admin.DevicePolicyManager; +import android.content.Intent; import android.content.res.ColorStateList; +import android.metrics.LogMaker; +import android.os.UserHandle; +import android.util.Log; +import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.nano.MetricsProto; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityContainer.BouncerUiEvent; import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback; +import com.android.keyguard.KeyguardSecurityContainer.SwipeListener; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.keyguard.dagger.KeyguardBouncerScope; +import com.android.settingslib.utils.ThreadUtils; +import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; import javax.inject.Inject; /** Controller for {@link KeyguardSecurityContainer} */ -public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer> { +@KeyguardBouncerScope +public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer> + implements KeyguardSecurityView { + + private static final boolean DEBUG = KeyguardConstants.DEBUG; + private static final String TAG = "KeyguardSecurityView"; + private final AdminSecondaryLockScreenController mAdminSecondaryLockScreenController; private final LockPatternUtils mLockPatternUtils; - private final KeyguardSecurityViewController.Factory mKeyguardSecurityViewControllerFactory; + private final KeyguardUpdateMonitor mUpdateMonitor; + private final KeyguardSecurityModel mSecurityModel; + private final MetricsLogger mMetricsLogger; + private final UiEventLogger mUiEventLogger; + private final KeyguardStateController mKeyguardStateController; + private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController; + private final SecurityCallback mSecurityCallback; + + private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid; + + private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() { + public void userActivity() { + if (mSecurityCallback != null) { + mSecurityCallback.userActivity(); + } + } + + @Override + public void onUserInput() { + mUpdateMonitor.cancelFaceAuth(); + } + + @Override + public void dismiss(boolean authenticated, int targetId) { + dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false); + } + + @Override + public void dismiss(boolean authenticated, int targetId, + boolean bypassSecondaryLockScreen) { + mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen); + } + + public boolean isVerifyUnlockOnly() { + return false; + } + + public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { + if (success) { + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, + SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS); + mLockPatternUtils.reportSuccessfulPasswordAttempt(userId); + // Force a garbage collection in an attempt to erase any lockscreen password left in + // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard + // dismiss animation janky. + ThreadUtils.postOnBackgroundThread(() -> { + try { + Thread.sleep(5000); + } catch (InterruptedException ignored) { } + Runtime.getRuntime().gc(); + }); + } else { + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, + SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE); + reportFailedUnlockAttempt(userId, timeoutMs); + } + mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) + .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE)); + mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS + : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE); + } + + public void reset() { + mSecurityCallback.reset(); + } + + public void onCancelClicked() { + mSecurityCallback.onCancelClicked(); + } + }; + - @Inject - KeyguardSecurityContainerController(KeyguardSecurityContainer view, + private SwipeListener mSwipeListener = new SwipeListener() { + @Override + public void onSwipeUp() { + if (!mUpdateMonitor.isFaceDetectionRunning()) { + mUpdateMonitor.requestFaceAuth(); + mKeyguardSecurityCallback.userActivity(); + showMessage(null, null); + } + } + }; + + private KeyguardSecurityContainerController(KeyguardSecurityContainer view, + AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory, LockPatternUtils lockPatternUtils, - KeyguardSecurityViewController.Factory keyguardSecurityViewControllerFactory) { + KeyguardUpdateMonitor keyguardUpdateMonitor, + KeyguardSecurityModel keyguardSecurityModel, + MetricsLogger metricsLogger, + UiEventLogger uiEventLogger, + KeyguardStateController keyguardStateController, + SecurityCallback securityCallback, + KeyguardSecurityViewFlipperController securityViewFlipperController) { super(view); mLockPatternUtils = lockPatternUtils; - view.setLockPatternUtils(mLockPatternUtils); - mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory; + mUpdateMonitor = keyguardUpdateMonitor; + mSecurityModel = keyguardSecurityModel; + mMetricsLogger = metricsLogger; + mUiEventLogger = uiEventLogger; + mKeyguardStateController = keyguardStateController; + mSecurityCallback = securityCallback; + mSecurityViewFlipperController = securityViewFlipperController; + mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create( + mKeyguardSecurityCallback); + } + + @Override + public void init() { + super.init(); + mSecurityViewFlipperController.init(); } @Override protected void onViewAttached() { + mView.setSwipeListener(mSwipeListener); } @Override @@ -51,68 +185,311 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard /** */ public void onPause() { + mAdminSecondaryLockScreenController.hide(); + if (mCurrentSecurityMode != SecurityMode.None) { + getCurrentSecurityController().onPause(); + } mView.onPause(); } + + /** + * Shows the primary security screen for the user. This will be either the multi-selector + * or the user's security method. + * @param turningOff true if the device is being turned off + */ public void showPrimarySecurityScreen(boolean turningOff) { - mView.showPrimarySecurityScreen(turningOff); + SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode( + KeyguardUpdateMonitor.getCurrentUser())); + if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")"); + showSecurityScreen(securityMode); } + @Override public void showPromptReason(int reason) { - mView.showPromptReason(reason); + if (mCurrentSecurityMode != SecurityMode.None) { + if (reason != PROMPT_REASON_NONE) { + Log.i(TAG, "Strong auth required, reason: " + reason); + } + getCurrentSecurityController().showPromptReason(reason); + } } public void showMessage(CharSequence message, ColorStateList colorState) { - mView.showMessage(message, colorState); + if (mCurrentSecurityMode != SecurityMode.None) { + getCurrentSecurityController().showMessage(message, colorState); + } } - public SecurityMode getCurrentSecuritySelection() { - return mView.getCurrentSecuritySelection(); + public SecurityMode getCurrentSecurityMode() { + return mCurrentSecurityMode; } public void dismiss(boolean authenticated, int targetUserId) { - mView.dismiss(authenticated, targetUserId); + mKeyguardSecurityCallback.dismiss(authenticated, targetUserId); } public void reset() { mView.reset(); + mSecurityViewFlipperController.reset(); } public CharSequence getTitle() { return mView.getTitle(); } - public void onResume(int screenOn) { - mView.onResume(screenOn); + @Override + public void onResume(int reason) { + if (mCurrentSecurityMode != SecurityMode.None) { + getCurrentSecurityController().onResume(reason); + } + mView.onResume( + mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()), + mKeyguardStateController.isFaceAuthEnabled()); } public void startAppearAnimation() { - mView.startAppearAnimation(); + if (mCurrentSecurityMode != SecurityMode.None) { + getCurrentSecurityController().startAppearAnimation(); + } } public boolean startDisappearAnimation(Runnable onFinishRunnable) { - return mView.startDisappearAnimation(onFinishRunnable); - } + mView.startDisappearAnimation(getCurrentSecurityMode()); - public void onStartingToHide() { - mView.onStartingToHide(); + if (mCurrentSecurityMode != SecurityMode.None) { + return getCurrentSecurityController().startDisappearAnimation(onFinishRunnable); + } + + return false; } - public void setSecurityCallback(SecurityCallback securityCallback) { - mView.setSecurityCallback(securityCallback); + public void onStartingToHide() { + if (mCurrentSecurityMode != SecurityMode.None) { + getCurrentSecurityController().onStartingToHide(); + } } + /** + * Shows the next security screen if there is one. + * @param authenticated true if the user entered the correct authentication + * @param targetUserId a user that needs to be the foreground user at the finish (if called) + * completion. + * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary + * secondary lock screen requirement, if any. + * @return true if keyguard is done + */ public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen) { - return mView.showNextSecurityScreenOrFinish( - authenticated, targetUserId, bypassSecondaryLockScreen); + + if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")"); + boolean finish = false; + boolean strongAuth = false; + int eventSubtype = -1; + BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN; + if (mUpdateMonitor.getUserHasTrust(targetUserId)) { + finish = true; + eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS; + uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS; + } else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) { + finish = true; + eventSubtype = BOUNCER_DISMISS_BIOMETRIC; + uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC; + } else if (SecurityMode.None == getCurrentSecurityMode()) { + SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); + if (SecurityMode.None == securityMode) { + finish = true; // no security required + eventSubtype = BOUNCER_DISMISS_NONE_SECURITY; + uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY; + } else { + showSecurityScreen(securityMode); // switch to the alternate security view + } + } else if (authenticated) { + switch (getCurrentSecurityMode()) { + case Pattern: + case Password: + case PIN: + strongAuth = true; + finish = true; + eventSubtype = BOUNCER_DISMISS_PASSWORD; + uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD; + break; + + case SimPin: + case SimPuk: + // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home + SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); + if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled( + KeyguardUpdateMonitor.getCurrentUser())) { + finish = true; + eventSubtype = BOUNCER_DISMISS_SIM; + uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM; + } else { + showSecurityScreen(securityMode); + } + break; + + default: + Log.v(TAG, "Bad security screen " + getCurrentSecurityMode() + + ", fail safe"); + showPrimarySecurityScreen(false); + break; + } + } + // Check for device admin specified additional security measures. + if (finish && !bypassSecondaryLockScreen) { + Intent secondaryLockscreenIntent = + mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId); + if (secondaryLockscreenIntent != null) { + mAdminSecondaryLockScreenController.show(secondaryLockscreenIntent); + return false; + } + } + if (eventSubtype != -1) { + mMetricsLogger.write(new LogMaker(MetricsProto.MetricsEvent.BOUNCER) + .setType(MetricsProto.MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype)); + } + if (uiEvent != BouncerUiEvent.UNKNOWN) { + mUiEventLogger.log(uiEvent); + } + if (finish) { + mSecurityCallback.finish(strongAuth, targetUserId); + } + return finish; } public boolean needsInput() { - return mView.needsInput(); + return getCurrentSecurityController().needsInput(); } - public SecurityMode getCurrentSecurityMode() { - return mView.getCurrentSecurityMode(); + /** + * Switches to the given security view unless it's already being shown, in which case + * this is a no-op. + * + * @param securityMode + */ + @VisibleForTesting + void showSecurityScreen(SecurityMode securityMode) { + if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")"); + + if (securityMode == SecurityMode.Invalid || securityMode == mCurrentSecurityMode) { + return; + } + + KeyguardInputViewController<KeyguardInputView> oldView = getCurrentSecurityController(); + + // Emulate Activity life cycle + if (oldView != null) { + oldView.onPause(); + } + + KeyguardInputViewController<KeyguardInputView> newView = changeSecurityMode(securityMode); + if (newView != null) { + newView.onResume(KeyguardSecurityView.VIEW_REVEALED); + mSecurityViewFlipperController.show(newView); + } + + mSecurityCallback.onSecurityModeChanged( + securityMode, newView != null && newView.needsInput()); + } + + public void reportFailedUnlockAttempt(int userId, int timeoutMs) { + // +1 for this time + final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1; + + if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts); + + final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager(); + final int failedAttemptsBeforeWipe = + dpm.getMaximumFailedPasswordsForWipe(null, userId); + + final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 + ? (failedAttemptsBeforeWipe - failedAttempts) + : Integer.MAX_VALUE; // because DPM returns 0 if no restriction + if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) { + // The user has installed a DevicePolicyManager that requests a user/profile to be wiped + // N attempts. Once we get below the grace period, we post this dialog every time as a + // clear warning until the deletion fires. + // Check which profile has the strictest policy for failed password attempts + final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId); + int userType = USER_TYPE_PRIMARY; + if (expiringUser == userId) { + // TODO: http://b/23522538 + if (expiringUser != UserHandle.USER_SYSTEM) { + userType = USER_TYPE_SECONDARY_USER; + } + } else if (expiringUser != UserHandle.USER_NULL) { + userType = USER_TYPE_WORK_PROFILE; + } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY + if (remainingBeforeWipe > 0) { + mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType); + } else { + // Too many attempts. The device will be wiped shortly. + Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!"); + mView.showWipeDialog(failedAttempts, userType); + } + } + mLockPatternUtils.reportFailedPasswordAttempt(userId); + if (timeoutMs > 0) { + mLockPatternUtils.reportPasswordLockout(timeoutMs, userId); + mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils, + mSecurityModel.getSecurityMode(userId)); + } + } + + private KeyguardInputViewController<KeyguardInputView> getCurrentSecurityController() { + return mSecurityViewFlipperController + .getSecurityView(mCurrentSecurityMode, mKeyguardSecurityCallback); + } + + private KeyguardInputViewController<KeyguardInputView> changeSecurityMode( + SecurityMode securityMode) { + mCurrentSecurityMode = securityMode; + return getCurrentSecurityController(); + } + + static class Factory { + + private final KeyguardSecurityContainer mView; + private final AdminSecondaryLockScreenController.Factory + mAdminSecondaryLockScreenControllerFactory; + private final LockPatternUtils mLockPatternUtils; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final KeyguardSecurityModel mKeyguardSecurityModel; + private final MetricsLogger mMetricsLogger; + private final UiEventLogger mUiEventLogger; + private final KeyguardStateController mKeyguardStateController; + private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController; + + @Inject + Factory(KeyguardSecurityContainer view, + AdminSecondaryLockScreenController.Factory + adminSecondaryLockScreenControllerFactory, + LockPatternUtils lockPatternUtils, + KeyguardUpdateMonitor keyguardUpdateMonitor, + KeyguardSecurityModel keyguardSecurityModel, + MetricsLogger metricsLogger, + UiEventLogger uiEventLogger, + KeyguardStateController keyguardStateController, + KeyguardSecurityViewFlipperController securityViewFlipperController) { + mView = view; + mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory; + mLockPatternUtils = lockPatternUtils; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mKeyguardSecurityModel = keyguardSecurityModel; + mMetricsLogger = metricsLogger; + mUiEventLogger = uiEventLogger; + mKeyguardStateController = keyguardStateController; + mSecurityViewFlipperController = securityViewFlipperController; + } + + public KeyguardSecurityContainerController create( + SecurityCallback securityCallback) { + return new KeyguardSecurityContainerController(mView, + mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, + mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, + mKeyguardStateController, securityCallback, mSecurityViewFlipperController); + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java index ac2160ecb4ae..c77c86711abf 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java @@ -18,13 +18,14 @@ package com.android.keyguard; import static com.android.systemui.DejankUtils.whitelistIpcs; import android.app.admin.DevicePolicyManager; -import android.content.Context; +import android.content.res.Resources; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dependency; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import javax.inject.Inject; @@ -33,7 +34,7 @@ public class KeyguardSecurityModel { /** * The different types of security available. - * @see KeyguardSecurityContainer#showSecurityScreen + * @see KeyguardSecurityContainerController#showSecurityScreen */ public enum SecurityMode { Invalid, // NULL state @@ -45,21 +46,15 @@ public class KeyguardSecurityModel { SimPuk // Unlock by entering a sim puk } - private final Context mContext; private final boolean mIsPukScreenAvailable; - private LockPatternUtils mLockPatternUtils; + private final LockPatternUtils mLockPatternUtils; @Inject - KeyguardSecurityModel(Context context) { - mContext = context; - mLockPatternUtils = new LockPatternUtils(context); - mIsPukScreenAvailable = mContext.getResources().getBoolean( + KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils) { + mIsPukScreenAvailable = resources.getBoolean( com.android.internal.R.bool.config_enable_puk_unlock_screen); - } - - void setLockPatternUtils(LockPatternUtils utils) { - mLockPatternUtils = utils; + mLockPatternUtils = lockPatternUtils; } public SecurityMode getSecurityMode(int userId) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java index 43cef3acf147..ac00e9453c97 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java @@ -18,11 +18,9 @@ package com.android.keyguard; import android.content.res.ColorStateList; import android.view.MotionEvent; -import com.android.internal.widget.LockPatternUtils; - public interface KeyguardSecurityView { - static public final int SCREEN_ON = 1; - static public final int VIEW_REVEALED = 2; + int SCREEN_ON = 1; + int VIEW_REVEALED = 2; int PROMPT_REASON_NONE = 0; @@ -63,18 +61,6 @@ public interface KeyguardSecurityView { int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7; /** - * Interface back to keyguard to tell it when security - * @param callback - */ - void setKeyguardCallback(KeyguardSecurityCallback callback); - - /** - * Set {@link LockPatternUtils} object. Useful for providing a mock interface. - * @param utils - */ - void setLockPatternUtils(LockPatternUtils utils); - - /** * Reset the view and prepare to take input. This should do things like clearing the * password or pattern and clear error messages. */ @@ -101,12 +87,6 @@ public interface KeyguardSecurityView { boolean needsInput(); /** - * Get {@link KeyguardSecurityCallback} for the given object - * @return KeyguardSecurityCallback - */ - KeyguardSecurityCallback getCallback(); - - /** * Show a string explaining why the security view needs to be solved. * * @param reason a flag indicating which string should be shown, see {@link #PROMPT_REASON_NONE} @@ -123,12 +103,6 @@ public interface KeyguardSecurityView { void showMessage(CharSequence message, ColorStateList colorState); /** - * Instruct the view to show usability hints, if any. - * - */ - void showUsabilityHint(); - - /** * Starts the animation which should run when the security view appears. */ void startAppearAnimation(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java deleted file mode 100644 index ef9ba19fbb43..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java +++ /dev/null @@ -1,58 +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.keyguard; - -import android.view.View; - -import com.android.systemui.util.ViewController; - -import javax.inject.Inject; - - -/** Controller for a {@link KeyguardSecurityView}. */ -public class KeyguardSecurityViewController extends ViewController<View> { - - private final KeyguardSecurityView mView; - - private KeyguardSecurityViewController(KeyguardSecurityView view) { - super((View) view); - // KeyguardSecurityView isn't actually a View, so we need to track it ourselves. - mView = view; - } - - @Override - protected void onViewAttached() { - - } - - @Override - protected void onViewDetached() { - - } - - /** Factory for a {@link KeyguardSecurityViewController}. */ - public static class Factory { - @Inject - public Factory() { - } - - /** Create a new {@link KeyguardSecurityViewController}. */ - public KeyguardSecurityViewController create(KeyguardSecurityView view) { - return new KeyguardSecurityViewController(view); - } - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java index 24da3ad46f23..b8439af6daaa 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java @@ -18,7 +18,6 @@ package com.android.keyguard; import android.annotation.NonNull; import android.content.Context; -import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Rect; import android.util.AttributeSet; @@ -31,7 +30,6 @@ import android.view.ViewHierarchyEncoder; import android.widget.FrameLayout; import android.widget.ViewFlipper; -import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; /** @@ -39,7 +37,7 @@ import com.android.systemui.R; * we can emulate {@link android.view.WindowManager.LayoutParams#FLAG_SLIPPERY} within a view * hierarchy. */ -public class KeyguardSecurityViewFlipper extends ViewFlipper implements KeyguardSecurityView { +public class KeyguardSecurityViewFlipper extends ViewFlipper { private static final String TAG = "KeyguardSecurityViewFlipper"; private static final boolean DEBUG = KeyguardConstants.DEBUG; @@ -69,111 +67,16 @@ public class KeyguardSecurityViewFlipper extends ViewFlipper implements Keyguard return result; } - KeyguardSecurityView getSecurityView() { + KeyguardInputView getSecurityView() { View child = getChildAt(getDisplayedChild()); - if (child instanceof KeyguardSecurityView) { - return (KeyguardSecurityView) child; + if (child instanceof KeyguardInputView) { + return (KeyguardInputView) child; } return null; } - @Override - public void setKeyguardCallback(KeyguardSecurityCallback callback) { - KeyguardSecurityView ksv = getSecurityView(); - if (ksv != null) { - ksv.setKeyguardCallback(callback); - } - } - - @Override - public void setLockPatternUtils(LockPatternUtils utils) { - KeyguardSecurityView ksv = getSecurityView(); - if (ksv != null) { - ksv.setLockPatternUtils(utils); - } - } - - @Override - public void reset() { - KeyguardSecurityView ksv = getSecurityView(); - if (ksv != null) { - ksv.reset(); - } - } - - @Override - public void onPause() { - KeyguardSecurityView ksv = getSecurityView(); - if (ksv != null) { - ksv.onPause(); - } - } - - @Override - public void onResume(int reason) { - KeyguardSecurityView ksv = getSecurityView(); - if (ksv != null) { - ksv.onResume(reason); - } - } - - @Override - public boolean needsInput() { - KeyguardSecurityView ksv = getSecurityView(); - return (ksv != null) ? ksv.needsInput() : false; - } - - @Override - public KeyguardSecurityCallback getCallback() { - KeyguardSecurityView ksv = getSecurityView(); - return (ksv != null) ? ksv.getCallback() : null; - } - - @Override - public void showPromptReason(int reason) { - KeyguardSecurityView ksv = getSecurityView(); - if (ksv != null) { - ksv.showPromptReason(reason); - } - } - - @Override - public void showMessage(CharSequence message, ColorStateList colorState) { - KeyguardSecurityView ksv = getSecurityView(); - if (ksv != null) { - ksv.showMessage(message, colorState); - } - } - - @Override - public void showUsabilityHint() { - KeyguardSecurityView ksv = getSecurityView(); - if (ksv != null) { - ksv.showUsabilityHint(); - } - } - - @Override - public void startAppearAnimation() { - KeyguardSecurityView ksv = getSecurityView(); - if (ksv != null) { - ksv.startAppearAnimation(); - } - } - - @Override - public boolean startDisappearAnimation(Runnable finishRunnable) { - KeyguardSecurityView ksv = getSecurityView(); - if (ksv != null) { - return ksv.startDisappearAnimation(finishRunnable); - } else { - return false; - } - } - - @Override public CharSequence getTitle() { - KeyguardSecurityView ksv = getSecurityView(); + KeyguardInputView ksv = getSecurityView(); if (ksv != null) { return ksv.getTitle(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java new file mode 100644 index 000000000000..49530355a6fb --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java @@ -0,0 +1,148 @@ +/* + * 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.keyguard; + +import android.util.Log; +import android.view.LayoutInflater; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.KeyguardInputViewController.Factory; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.keyguard.dagger.KeyguardBouncerScope; +import com.android.systemui.R; +import com.android.systemui.util.ViewController; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +/** + * Controller for a {@link KeyguardSecurityViewFlipper}. + */ +@KeyguardBouncerScope +public class KeyguardSecurityViewFlipperController + extends ViewController<KeyguardSecurityViewFlipper> { + + private static final boolean DEBUG = KeyguardConstants.DEBUG; + private static final String TAG = "KeyguardSecurityView"; + + private final List<KeyguardInputViewController<KeyguardInputView>> mChildren = + new ArrayList<>(); + private final LayoutInflater mLayoutInflater; + private final Factory mKeyguardSecurityViewControllerFactory; + + @Inject + protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view, + LayoutInflater layoutInflater, + KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory) { + super(view); + mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory; + mLayoutInflater = layoutInflater; + } + + @Override + protected void onViewAttached() { + + } + + @Override + protected void onViewDetached() { + + } + + public void reset() { + for (KeyguardInputViewController<KeyguardInputView> child : mChildren) { + child.reset(); + } + } + + @VisibleForTesting + KeyguardInputViewController<KeyguardInputView> getSecurityView(SecurityMode securityMode, + KeyguardSecurityCallback keyguardSecurityCallback) { + KeyguardInputViewController<KeyguardInputView> childController = null; + for (KeyguardInputViewController<KeyguardInputView> child : mChildren) { + if (child.getSecurityMode() == securityMode) { + childController = child; + break; + } + } + + if (childController == null + && securityMode != SecurityMode.None && securityMode != SecurityMode.Invalid) { + + int layoutId = getLayoutIdFor(securityMode); + KeyguardInputView view = null; + if (layoutId != 0) { + if (DEBUG) Log.v(TAG, "inflating id = " + layoutId); + view = (KeyguardInputView) mLayoutInflater.inflate( + layoutId, mView, false); + mView.addView(view); + childController = mKeyguardSecurityViewControllerFactory.create( + view, securityMode, keyguardSecurityCallback); + childController.init(); + + mChildren.add(childController); + } + } + + if (childController == null) { + childController = new NullKeyguardInputViewController( + securityMode, keyguardSecurityCallback); + } + + return childController; + } + + private int getLayoutIdFor(SecurityMode securityMode) { + switch (securityMode) { + case Pattern: return com.android.systemui.R.layout.keyguard_pattern_view; + case PIN: return com.android.systemui.R.layout.keyguard_pin_view; + case Password: return com.android.systemui.R.layout.keyguard_password_view; + case SimPin: return com.android.systemui.R.layout.keyguard_sim_pin_view; + case SimPuk: return R.layout.keyguard_sim_puk_view; + default: + return 0; + } + } + + /** Makes the supplied child visible if it is contained win this view, */ + public void show(KeyguardInputViewController<KeyguardInputView> childController) { + int index = childController.getIndexIn(mView); + if (index != -1) { + mView.setDisplayedChild(index); + } + } + + private static class NullKeyguardInputViewController + extends KeyguardInputViewController<KeyguardInputView> { + protected NullKeyguardInputViewController(SecurityMode securityMode, + KeyguardSecurityCallback keyguardSecurityCallback) { + super(null, securityMode, keyguardSecurityCallback); + } + + @Override + public boolean needsInput() { + return false; + } + + @Override + public void onStartingToHide() { + + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java index 1c47aa0151f0..c0f9ce794628 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java @@ -16,66 +16,19 @@ package com.android.keyguard; -import android.annotation.NonNull; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.Dialog; -import android.app.ProgressDialog; import android.content.Context; -import android.content.res.ColorStateList; import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.telephony.PinResult; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; import android.util.AttributeSet; -import android.util.Log; import android.view.View; -import android.view.WindowManager; -import android.widget.ImageView; -import com.android.systemui.Dependency; import com.android.systemui.R; /** * Displays a PIN pad for unlocking. */ public class KeyguardSimPinView extends KeyguardPinBasedInputView { - private static final String LOG_TAG = "KeyguardSimPinView"; - private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES; public static final String TAG = "KeyguardSimPinView"; - private ProgressDialog mSimUnlockProgressDialog = null; - private CheckSimPin mCheckSimPinThread; - - // Below flag is set to true during power-up or when a new SIM card inserted on device. - // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would - // be displayed to inform user about the number of remaining PIN attempts left. - private boolean mShowDefaultMessage = true; - private int mRemainingAttempts = -1; - private AlertDialog mRemainingAttemptsDialog; - private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - private ImageView mSimImageView; - - KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onSimStateChanged(int subId, int slotId, int simState) { - if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); - switch(simState) { - case TelephonyManager.SIM_STATE_READY: { - mRemainingAttempts = -1; - resetState(); - break; - } - default: - resetState(); - } - } - }; - public KeyguardSimPinView(Context context) { this(context, null); } @@ -84,81 +37,9 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { super(context, attrs); } - @Override - public void resetState() { - super.resetState(); - if (DEBUG) Log.v(TAG, "Resetting state"); - handleSubInfoChangeIfNeeded(); - if (mShowDefaultMessage) { - showDefaultMessage(); - } - boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); - + public void setEsimLocked(boolean locked) { KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area); - esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); - } - - private void setLockedSimMessage() { - boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); - int count = 1; - TelephonyManager telephonyManager = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - if (telephonyManager != null) { - count = telephonyManager.getActiveModemCount(); - } - Resources rez = getResources(); - String msg; - TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor }); - int color = array.getColor(0, Color.WHITE); - array.recycle(); - if (count < 2) { - msg = rez.getString(R.string.kg_sim_pin_instructions); - } else { - SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class) - .getSubscriptionInfoForSubId(mSubId); - CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash - msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); - if (info != null) { - color = info.getIconTint(); - } - } - if (isEsimLocked) { - msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); - } - - if (mSecurityMessageDisplay != null && getVisibility() == VISIBLE) { - mSecurityMessageDisplay.setMessage(msg); - } - mSimImageView.setImageTintList(ColorStateList.valueOf(color)); - } - - private void showDefaultMessage() { - setLockedSimMessage(); - if (mRemainingAttempts >= 0) { - return; - } - - // Sending empty PIN here to query the number of remaining PIN attempts - new CheckSimPin("", mSubId) { - void onSimCheckResponse(final PinResult result) { - Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result " - + result.toString()); - if (result.getAttemptsRemaining() >= 0) { - mRemainingAttempts = result.getAttemptsRemaining(); - setLockedSimMessage(); - } - } - }.start(); - } - - private void handleSubInfoChangeIfNeeded() { - KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class); - int subId = monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED); - if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { - mSubId = subId; - mShowDefaultMessage = true; - mRemainingAttempts = -1; - } + esimButton.setVisibility(locked ? View.VISIBLE : View.GONE); } @Override @@ -173,35 +54,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { return 0; } - private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) { - String displayMessage; - int msgId; - if (attemptsRemaining == 0) { - displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked); - } else if (attemptsRemaining > 0) { - msgId = isDefault ? R.plurals.kg_password_default_pin_message : - R.plurals.kg_password_wrong_pin_code; - displayMessage = getContext().getResources() - .getQuantityString(msgId, attemptsRemaining, attemptsRemaining); - } else { - msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed; - displayMessage = getContext().getString(msgId); - } - if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { - displayMessage = getResources() - .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); - } - if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:" - + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); - return displayMessage; - } - - @Override - protected boolean shouldLockout(long deadline) { - // SIM PIN doesn't have a timed lockout - return false; - } - @Override protected int getPasswordTextViewId() { return R.id.simPinEntry; @@ -214,173 +66,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { if (mEcaView instanceof EmergencyCarrierArea) { ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); } - mSimImageView = findViewById(R.id.keyguard_sim); - } - - @Override - public void showUsabilityHint() { - - } - - @Override - public void onResume(int reason) { - super.onResume(reason); - Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback); - resetState(); - } - - @Override - public void onPause() { - // dismiss the dialog. - if (mSimUnlockProgressDialog != null) { - mSimUnlockProgressDialog.dismiss(); - mSimUnlockProgressDialog = null; - } - Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback); - } - - /** - * Since the IPC can block, we want to run the request in a separate thread - * with a callback. - */ - private abstract class CheckSimPin extends Thread { - private final String mPin; - private int mSubId; - - protected CheckSimPin(String pin, int subId) { - mPin = pin; - mSubId = subId; - } - - abstract void onSimCheckResponse(@NonNull PinResult result); - - @Override - public void run() { - if (DEBUG) { - Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")"); - } - TelephonyManager telephonyManager = - ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) - .createForSubscriptionId(mSubId); - final PinResult result = telephonyManager.supplyPinReportPinResult(mPin); - if (result == null) { - Log.e(TAG, "Error result for supplyPinReportResult."); - post(new Runnable() { - @Override - public void run() { - onSimCheckResponse(PinResult.getDefaultFailedResult()); - } - }); - } else { - if (DEBUG) { - Log.v(TAG, "supplyPinReportResult returned: " + result.toString()); - } - post(new Runnable() { - @Override - public void run() { - onSimCheckResponse(result); - } - }); - } - } - } - - private Dialog getSimUnlockProgressDialog() { - if (mSimUnlockProgressDialog == null) { - mSimUnlockProgressDialog = new ProgressDialog(mContext); - mSimUnlockProgressDialog.setMessage( - mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); - mSimUnlockProgressDialog.setIndeterminate(true); - mSimUnlockProgressDialog.setCancelable(false); - mSimUnlockProgressDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } - return mSimUnlockProgressDialog; - } - - private Dialog getSimRemainingAttemptsDialog(int remaining) { - String msg = getPinPasswordErrorMessage(remaining, false); - if (mRemainingAttemptsDialog == null) { - Builder builder = new AlertDialog.Builder(mContext); - builder.setMessage(msg); - builder.setCancelable(false); - builder.setNeutralButton(R.string.ok, null); - mRemainingAttemptsDialog = builder.create(); - mRemainingAttemptsDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } else { - mRemainingAttemptsDialog.setMessage(msg); - } - return mRemainingAttemptsDialog; - } - - @Override - protected void verifyPasswordAndUnlock() { - String entry = mPasswordEntry.getText(); - - if (entry.length() < 4) { - // otherwise, display a message to the user, and don't submit. - mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint); - resetPasswordText(true /* animate */, true /* announce */); - mCallback.userActivity(); - return; - } - - getSimUnlockProgressDialog().show(); - - if (mCheckSimPinThread == null) { - mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) { - @Override - void onSimCheckResponse(final PinResult result) { - post(new Runnable() { - @Override - public void run() { - mRemainingAttempts = result.getAttemptsRemaining(); - if (mSimUnlockProgressDialog != null) { - mSimUnlockProgressDialog.hide(); - } - resetPasswordText(true /* animate */, - /* announce */ - result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); - if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { - Dependency.get(KeyguardUpdateMonitor.class) - .reportSimUnlocked(mSubId); - mRemainingAttempts = -1; - mShowDefaultMessage = true; - if (mCallback != null) { - mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); - } - } else { - mShowDefaultMessage = false; - if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { - if (result.getAttemptsRemaining() <= 2) { - // this is getting critical - show dialog - getSimRemainingAttemptsDialog( - result.getAttemptsRemaining()).show(); - } else { - // show message - mSecurityMessageDisplay.setMessage( - getPinPasswordErrorMessage( - result.getAttemptsRemaining(), false)); - } - } else { - // "PIN operation failed!" - no idea what this was and no way to - // find out. :/ - mSecurityMessageDisplay.setMessage(getContext().getString( - R.string.kg_password_pin_failed)); - } - if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " - + " CheckSimPin.onSimCheckResponse: " + result - + " attemptsRemaining=" + result.getAttemptsRemaining()); - } - mCallback.userActivity(); - mCheckSimPinThread = null; - } - }); - } - }; - mCheckSimPinThread.start(); - } } @Override @@ -389,11 +74,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { } @Override - public boolean startDisappearAnimation(Runnable finishRunnable) { - return false; - } - - @Override public CharSequence getTitle() { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_sim_pin_unlock); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java new file mode 100644 index 000000000000..cc8bf4f2d028 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -0,0 +1,350 @@ +/* + * 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.keyguard; + +import android.annotation.NonNull; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.telephony.PinResult; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; + +public class KeyguardSimPinViewController + extends KeyguardPinBasedInputViewController<KeyguardSimPinView> { + public static final String TAG = "KeyguardSimPinView"; + private static final String LOG_TAG = "KeyguardSimPinView"; + private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final TelephonyManager mTelephonyManager; + + private ProgressDialog mSimUnlockProgressDialog; + private CheckSimPin mCheckSimPinThread; + private int mRemainingAttempts; + // Below flag is set to true during power-up or when a new SIM card inserted on device. + // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would + // be displayed to inform user about the number of remaining PIN attempts left. + private boolean mShowDefaultMessage; + private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private AlertDialog mRemainingAttemptsDialog; + private ImageView mSimImageView; + + KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onSimStateChanged(int subId, int slotId, int simState) { + if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); + if (simState == TelephonyManager.SIM_STATE_READY) { + mRemainingAttempts = -1; + resetState(); + } else { + resetState(); + } + } + }; + + protected KeyguardSimPinViewController(KeyguardSimPinView view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + LatencyTracker latencyTracker, + LiftToActivateListener liftToActivateListener, + TelephonyManager telephonyManager) { + super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, + messageAreaControllerFactory, latencyTracker, liftToActivateListener); + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mTelephonyManager = telephonyManager; + mSimImageView = mView.findViewById(R.id.keyguard_sim); + } + + @Override + protected void onViewAttached() { + super.onViewAttached(); + } + + @Override + void resetState() { + super.resetState(); + if (DEBUG) Log.v(TAG, "Resetting state"); + handleSubInfoChangeIfNeeded(); + mMessageAreaController.setMessage(""); + if (mShowDefaultMessage) { + showDefaultMessage(); + } + + mView.setEsimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)); + } + + @Override + public boolean startDisappearAnimation(Runnable finishRunnable) { + return false; + } + + @Override + public void onResume(int reason) { + super.onResume(reason); + mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); + mView.resetState(); + } + + @Override + public void onPause() { + super.onPause(); + mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); + + // dismiss the dialog. + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.dismiss(); + mSimUnlockProgressDialog = null; + } + } + + @Override + protected void verifyPasswordAndUnlock() { + String entry = mPasswordEntry.getText(); + + if (entry.length() < 4) { + // otherwise, display a message to the user, and don't submit. + mMessageAreaController.setMessage( + com.android.systemui.R.string.kg_invalid_sim_pin_hint); + mView.resetPasswordText(true /* animate */, true /* announce */); + getKeyguardSecurityCallback().userActivity(); + return; + } + + getSimUnlockProgressDialog().show(); + + if (mCheckSimPinThread == null) { + mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) { + @Override + void onSimCheckResponse(final PinResult result) { + mView.post(() -> { + mRemainingAttempts = result.getAttemptsRemaining(); + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } + mView.resetPasswordText(true /* animate */, + /* announce */ + result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); + if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { + mKeyguardUpdateMonitor.reportSimUnlocked(mSubId); + mRemainingAttempts = -1; + mShowDefaultMessage = true; + getKeyguardSecurityCallback().dismiss( + true, KeyguardUpdateMonitor.getCurrentUser()); + } else { + mShowDefaultMessage = false; + if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { + if (result.getAttemptsRemaining() <= 2) { + // this is getting critical - show dialog + getSimRemainingAttemptsDialog( + result.getAttemptsRemaining()).show(); + } else { + // show message + mMessageAreaController.setMessage( + getPinPasswordErrorMessage( + result.getAttemptsRemaining(), false)); + } + } else { + // "PIN operation failed!" - no idea what this was and no way to + // find out. :/ + mMessageAreaController.setMessage(mView.getResources().getString( + R.string.kg_password_pin_failed)); + } + if (DEBUG) { + Log.d(LOG_TAG, "verifyPasswordAndUnlock " + + " CheckSimPin.onSimCheckResponse: " + result + + " attemptsRemaining=" + result.getAttemptsRemaining()); + } + } + getKeyguardSecurityCallback().userActivity(); + mCheckSimPinThread = null; + }); + } + }; + mCheckSimPinThread.start(); + } + } + + private Dialog getSimUnlockProgressDialog() { + if (mSimUnlockProgressDialog == null) { + mSimUnlockProgressDialog = new ProgressDialog(mView.getContext()); + mSimUnlockProgressDialog.setMessage( + mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message)); + mSimUnlockProgressDialog.setIndeterminate(true); + mSimUnlockProgressDialog.setCancelable(false); + mSimUnlockProgressDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + return mSimUnlockProgressDialog; + } + + + private Dialog getSimRemainingAttemptsDialog(int remaining) { + String msg = getPinPasswordErrorMessage(remaining, false); + if (mRemainingAttemptsDialog == null) { + Builder builder = new AlertDialog.Builder(mView.getContext()); + builder.setMessage(msg); + builder.setCancelable(false); + builder.setNeutralButton(R.string.ok, null); + mRemainingAttemptsDialog = builder.create(); + mRemainingAttemptsDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } else { + mRemainingAttemptsDialog.setMessage(msg); + } + return mRemainingAttemptsDialog; + } + + + private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) { + String displayMessage; + int msgId; + if (attemptsRemaining == 0) { + displayMessage = mView.getResources().getString( + R.string.kg_password_wrong_pin_code_pukked); + } else if (attemptsRemaining > 0) { + msgId = isDefault ? R.plurals.kg_password_default_pin_message : + R.plurals.kg_password_wrong_pin_code; + displayMessage = mView.getResources() + .getQuantityString(msgId, attemptsRemaining, attemptsRemaining); + } else { + msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed; + displayMessage = mView.getResources().getString(msgId); + } + if (KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)) { + displayMessage = mView.getResources() + .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); + } + if (DEBUG) { + Log.d(LOG_TAG, "getPinPasswordErrorMessage: attemptsRemaining=" + + attemptsRemaining + " displayMessage=" + displayMessage); + } + return displayMessage; + } + + private void showDefaultMessage() { + setLockedSimMessage(); + if (mRemainingAttempts >= 0) { + return; + } + + // Sending empty PIN here to query the number of remaining PIN attempts + new CheckSimPin("", mSubId) { + void onSimCheckResponse(final PinResult result) { + Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result " + + result.toString()); + if (result.getAttemptsRemaining() >= 0) { + mRemainingAttempts = result.getAttemptsRemaining(); + setLockedSimMessage(); + } + } + }.start(); + } + + /** + * Since the IPC can block, we want to run the request in a separate thread + * with a callback. + */ + private abstract class CheckSimPin extends Thread { + private final String mPin; + private int mSubId; + + protected CheckSimPin(String pin, int subId) { + mPin = pin; + mSubId = subId; + } + + abstract void onSimCheckResponse(@NonNull PinResult result); + + @Override + public void run() { + if (DEBUG) { + Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")"); + } + TelephonyManager telephonyManager = + mTelephonyManager.createForSubscriptionId(mSubId); + final PinResult result = telephonyManager.supplyPinReportPinResult(mPin); + if (result == null) { + Log.e(TAG, "Error result for supplyPinReportResult."); + mView.post(() -> onSimCheckResponse(PinResult.getDefaultFailedResult())); + } else { + if (DEBUG) { + Log.v(TAG, "supplyPinReportResult returned: " + result.toString()); + } + mView.post(() -> onSimCheckResponse(result)); + } + } + } + + private void setLockedSimMessage() { + boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); + int count = 1; + if (mTelephonyManager != null) { + count = mTelephonyManager.getActiveModemCount(); + } + Resources rez = mView.getResources(); + String msg; + TypedArray array = mView.getContext().obtainStyledAttributes( + new int[] { R.attr.wallpaperTextColor }); + int color = array.getColor(0, Color.WHITE); + array.recycle(); + if (count < 2) { + msg = rez.getString(R.string.kg_sim_pin_instructions); + } else { + SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); + CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash + msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); + if (info != null) { + color = info.getIconTint(); + } + } + if (isEsimLocked) { + msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); + } + + if (mView.getVisibility() == View.VISIBLE) { + mMessageAreaController.setMessage(msg); + } + mSimImageView.setImageTintList(ColorStateList.valueOf(color)); + } + + private void handleSubInfoChangeIfNeeded() { + int subId = mKeyguardUpdateMonitor + .getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED); + if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { + mSubId = subId; + mShowDefaultMessage = true; + mRemainingAttempts = -1; + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java index 5148dd709026..0d72c93e9041 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java @@ -16,27 +16,10 @@ package com.android.keyguard; -import android.annotation.NonNull; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ProgressDialog; import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.telephony.PinResult; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; import android.util.AttributeSet; import android.util.Log; -import android.view.View; -import android.view.WindowManager; -import android.widget.ImageView; -import com.android.systemui.Dependency; import com.android.systemui.R; @@ -44,48 +27,9 @@ import com.android.systemui.R; * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier. */ public class KeyguardSimPukView extends KeyguardPinBasedInputView { - private static final String LOG_TAG = "KeyguardSimPukView"; private static final boolean DEBUG = KeyguardConstants.DEBUG; public static final String TAG = "KeyguardSimPukView"; - private ProgressDialog mSimUnlockProgressDialog = null; - private CheckSimPuk mCheckSimPukThread; - - // Below flag is set to true during power-up or when a new SIM card inserted on device. - // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would - // be displayed to inform user about the number of remaining PUK attempts left. - private boolean mShowDefaultMessage = true; - private int mRemainingAttempts = -1; - private String mPukText; - private String mPinText; - private StateMachine mStateMachine = new StateMachine(); - private AlertDialog mRemainingAttemptsDialog; - private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - private ImageView mSimImageView; - - KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onSimStateChanged(int subId, int slotId, int simState) { - if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); - switch(simState) { - // If the SIM is unlocked via a key sequence through the emergency dialer, it will - // move into the READY state and the PUK lock keyguard should be removed. - case TelephonyManager.SIM_STATE_READY: { - mRemainingAttempts = -1; - mShowDefaultMessage = true; - // mCallback can be null if onSimStateChanged callback is called when keyguard - // isn't active. - if (mCallback != null) { - mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); - } - break; - } - default: - resetState(); - } - } - }; - public KeyguardSimPukView(Context context) { this(context, null); } @@ -94,136 +38,14 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { super(context, attrs); } - private class StateMachine { - final int ENTER_PUK = 0; - final int ENTER_PIN = 1; - final int CONFIRM_PIN = 2; - final int DONE = 3; - private int state = ENTER_PUK; - - public void next() { - int msg = 0; - if (state == ENTER_PUK) { - if (checkPuk()) { - state = ENTER_PIN; - msg = R.string.kg_puk_enter_pin_hint; - } else { - msg = R.string.kg_invalid_sim_puk_hint; - } - } else if (state == ENTER_PIN) { - if (checkPin()) { - state = CONFIRM_PIN; - msg = R.string.kg_enter_confirm_pin_hint; - } else { - msg = R.string.kg_invalid_sim_pin_hint; - } - } else if (state == CONFIRM_PIN) { - if (confirmPin()) { - state = DONE; - msg = R.string.keyguard_sim_unlock_progress_dialog_message; - updateSim(); - } else { - state = ENTER_PIN; // try again? - msg = R.string.kg_invalid_confirm_pin_hint; - } - } - resetPasswordText(true /* animate */, true /* announce */); - if (msg != 0) { - mSecurityMessageDisplay.setMessage(msg); - } - } - - - void reset() { - mPinText=""; - mPukText=""; - state = ENTER_PUK; - handleSubInfoChangeIfNeeded(); - if (mShowDefaultMessage) { - showDefaultMessage(); - } - boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); - - KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area); - esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); - mPasswordEntry.requestFocus(); - } - - - } - - private void showDefaultMessage() { - if (mRemainingAttempts >= 0) { - mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage( - mRemainingAttempts, true)); - return; - } - - boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); - int count = 1; - TelephonyManager telephonyManager = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - if (telephonyManager != null) { - count = telephonyManager.getActiveModemCount(); - } - Resources rez = getResources(); - String msg; - TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor }); - int color = array.getColor(0, Color.WHITE); - array.recycle(); - if (count < 2) { - msg = rez.getString(R.string.kg_puk_enter_puk_hint); - } else { - SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class) - .getSubscriptionInfoForSubId(mSubId); - CharSequence displayName = info != null ? info.getDisplayName() : ""; - msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); - if (info != null) { - color = info.getIconTint(); - } - } - if (isEsimLocked) { - msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); - } - if (mSecurityMessageDisplay != null) { - mSecurityMessageDisplay.setMessage(msg); - } - mSimImageView.setImageTintList(ColorStateList.valueOf(color)); - - // Sending empty PUK here to query the number of remaining PIN attempts - new CheckSimPuk("", "", mSubId) { - void onSimLockChangedResponse(final PinResult result) { - if (result == null) Log.e(LOG_TAG, "onSimCheckResponse, pin result is NULL"); - else { - Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result " - + result.toString()); - if (result.getAttemptsRemaining() >= 0) { - mRemainingAttempts = result.getAttemptsRemaining(); - mSecurityMessageDisplay.setMessage( - getPukPasswordErrorMessage(result.getAttemptsRemaining(), true)); - } - } - } - }.start(); - } - - private void handleSubInfoChangeIfNeeded() { - KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class); - int subId = monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED); - if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { - mSubId = subId; - mShowDefaultMessage = true; - mRemainingAttempts = -1; - } - } - @Override protected int getPromptReasonStringRes(int reason) { // No message on SIM Puk return 0; } - private String getPukPasswordErrorMessage(int attemptsRemaining, boolean isDefault) { + String getPukPasswordErrorMessage( + int attemptsRemaining, boolean isDefault, boolean isEsimLocked) { String displayMessage; if (attemptsRemaining == 0) { @@ -238,28 +60,19 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { R.string.kg_password_puk_failed; displayMessage = getContext().getString(msgId); } - if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { + if (isEsimLocked) { displayMessage = getResources() .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); } - if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:" - + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); + if (DEBUG) { + Log.d(TAG, "getPukPasswordErrorMessage:" + + " attemptsRemaining=" + attemptsRemaining + + " displayMessage=" + displayMessage); + } return displayMessage; } @Override - public void resetState() { - super.resetState(); - mStateMachine.reset(); - } - - @Override - protected boolean shouldLockout(long deadline) { - // SIM PUK doesn't have a timed lockout - return false; - } - - @Override protected int getPasswordTextViewId() { return R.id.pukEntry; } @@ -271,197 +84,6 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { if (mEcaView instanceof EmergencyCarrierArea) { ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); } - mSimImageView = findViewById(R.id.keyguard_sim); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback); - resetState(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback); - } - - @Override - public void showUsabilityHint() { - } - - @Override - public void onPause() { - // dismiss the dialog. - if (mSimUnlockProgressDialog != null) { - mSimUnlockProgressDialog.dismiss(); - mSimUnlockProgressDialog = null; - } - } - - /** - * Since the IPC can block, we want to run the request in a separate thread - * with a callback. - */ - private abstract class CheckSimPuk extends Thread { - - private final String mPin, mPuk; - private final int mSubId; - - protected CheckSimPuk(String puk, String pin, int subId) { - mPuk = puk; - mPin = pin; - mSubId = subId; - } - - abstract void onSimLockChangedResponse(@NonNull PinResult result); - - @Override - public void run() { - if (DEBUG) Log.v(TAG, "call supplyPukReportResult()"); - TelephonyManager telephonyManager = - ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) - .createForSubscriptionId(mSubId); - final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin); - if (result == null) { - Log.e(TAG, "Error result for supplyPukReportResult."); - post(new Runnable() { - @Override - public void run() { - onSimLockChangedResponse(PinResult.getDefaultFailedResult()); - } - }); - } else { - if (DEBUG) { - Log.v(TAG, "supplyPukReportResult returned: " + result.toString()); - } - post(new Runnable() { - @Override - public void run() { - onSimLockChangedResponse(result); - } - }); - } - } - } - - private Dialog getSimUnlockProgressDialog() { - if (mSimUnlockProgressDialog == null) { - mSimUnlockProgressDialog = new ProgressDialog(mContext); - mSimUnlockProgressDialog.setMessage( - mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); - mSimUnlockProgressDialog.setIndeterminate(true); - mSimUnlockProgressDialog.setCancelable(false); - if (!(mContext instanceof Activity)) { - mSimUnlockProgressDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } - } - return mSimUnlockProgressDialog; - } - - private Dialog getPukRemainingAttemptsDialog(int remaining) { - String msg = getPukPasswordErrorMessage(remaining, false); - if (mRemainingAttemptsDialog == null) { - AlertDialog.Builder builder = new AlertDialog.Builder(mContext); - builder.setMessage(msg); - builder.setCancelable(false); - builder.setNeutralButton(R.string.ok, null); - mRemainingAttemptsDialog = builder.create(); - mRemainingAttemptsDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } else { - mRemainingAttemptsDialog.setMessage(msg); - } - return mRemainingAttemptsDialog; - } - - private boolean checkPuk() { - // make sure the puk is at least 8 digits long. - if (mPasswordEntry.getText().length() == 8) { - mPukText = mPasswordEntry.getText(); - return true; - } - return false; - } - - private boolean checkPin() { - // make sure the PIN is between 4 and 8 digits - int length = mPasswordEntry.getText().length(); - if (length >= 4 && length <= 8) { - mPinText = mPasswordEntry.getText(); - return true; - } - return false; - } - - public boolean confirmPin() { - return mPinText.equals(mPasswordEntry.getText()); - } - - private void updateSim() { - getSimUnlockProgressDialog().show(); - - if (mCheckSimPukThread == null) { - mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) { - @Override - void onSimLockChangedResponse(final PinResult result) { - post(new Runnable() { - @Override - public void run() { - if (mSimUnlockProgressDialog != null) { - mSimUnlockProgressDialog.hide(); - } - resetPasswordText(true /* animate */, - /* announce */ - result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); - if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { - Dependency.get(KeyguardUpdateMonitor.class) - .reportSimUnlocked(mSubId); - mRemainingAttempts = -1; - mShowDefaultMessage = true; - if (mCallback != null) { - mCallback.dismiss(true, - KeyguardUpdateMonitor.getCurrentUser()); - } - } else { - mShowDefaultMessage = false; - if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { - // show message - mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage( - result.getAttemptsRemaining(), false)); - if (result.getAttemptsRemaining() <= 2) { - // this is getting critical - show dialog - getPukRemainingAttemptsDialog( - result.getAttemptsRemaining()).show(); - } else { - // show message - mSecurityMessageDisplay.setMessage( - getPukPasswordErrorMessage( - result.getAttemptsRemaining(), false)); - } - } else { - mSecurityMessageDisplay.setMessage(getContext().getString( - R.string.kg_password_puk_failed)); - } - if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " - + " UpdateSim.onSimCheckResponse: " - + " attemptsRemaining=" + result.getAttemptsRemaining()); - } - mStateMachine.reset(); - mCheckSimPukThread = null; - } - }); - } - }; - mCheckSimPukThread.start(); - } - } - - @Override - protected void verifyPasswordAndUnlock() { - mStateMachine.next(); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java new file mode 100644 index 000000000000..a87374939ba6 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -0,0 +1,413 @@ +/* + * 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.keyguard; + +import android.annotation.NonNull; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.telephony.PinResult; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.Dependency; +import com.android.systemui.R; + +public class KeyguardSimPukViewController + extends KeyguardPinBasedInputViewController<KeyguardSimPukView> { + private static final boolean DEBUG = KeyguardConstants.DEBUG; + public static final String TAG = "KeyguardSimPukView"; + + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final TelephonyManager mTelephonyManager; + + private String mPukText; + private String mPinText; + private int mRemainingAttempts; + // Below flag is set to true during power-up or when a new SIM card inserted on device. + // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would + // be displayed to inform user about the number of remaining PUK attempts left. + private boolean mShowDefaultMessage; + private StateMachine mStateMachine = new StateMachine(); + private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private CheckSimPuk mCheckSimPukThread; + private ProgressDialog mSimUnlockProgressDialog; + + KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onSimStateChanged(int subId, int slotId, int simState) { + if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); + // If the SIM is unlocked via a key sequence through the emergency dialer, it will + // move into the READY state and the PUK lock keyguard should be removed. + if (simState == TelephonyManager.SIM_STATE_READY) { + mRemainingAttempts = -1; + mShowDefaultMessage = true; + getKeyguardSecurityCallback().dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); + } else { + resetState(); + } + } + }; + private ImageView mSimImageView; + private AlertDialog mRemainingAttemptsDialog; + + protected KeyguardSimPukViewController(KeyguardSimPukView view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + LatencyTracker latencyTracker, + LiftToActivateListener liftToActivateListener, + TelephonyManager telephonyManager) { + super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, + messageAreaControllerFactory, latencyTracker, liftToActivateListener); + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mTelephonyManager = telephonyManager; + mSimImageView = mView.findViewById(R.id.keyguard_sim); + } + + @Override + protected void onViewAttached() { + super.onViewAttached(); + mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); + } + + @Override + protected void onViewDetached() { + super.onViewDetached(); + mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); + } + + @Override + void resetState() { + super.resetState(); + mStateMachine.reset(); + } + + @Override + protected void verifyPasswordAndUnlock() { + mStateMachine.next(); + } + + private class StateMachine { + static final int ENTER_PUK = 0; + static final int ENTER_PIN = 1; + static final int CONFIRM_PIN = 2; + static final int DONE = 3; + + private int mState = ENTER_PUK; + + public void next() { + int msg = 0; + if (mState == ENTER_PUK) { + if (checkPuk()) { + mState = ENTER_PIN; + msg = com.android.systemui.R.string.kg_puk_enter_pin_hint; + } else { + msg = com.android.systemui.R.string.kg_invalid_sim_puk_hint; + } + } else if (mState == ENTER_PIN) { + if (checkPin()) { + mState = CONFIRM_PIN; + msg = com.android.systemui.R.string.kg_enter_confirm_pin_hint; + } else { + msg = com.android.systemui.R.string.kg_invalid_sim_pin_hint; + } + } else if (mState == CONFIRM_PIN) { + if (confirmPin()) { + mState = DONE; + msg = com.android.systemui.R.string.keyguard_sim_unlock_progress_dialog_message; + updateSim(); + } else { + mState = ENTER_PIN; // try again? + msg = com.android.systemui.R.string.kg_invalid_confirm_pin_hint; + } + } + mView.resetPasswordText(true /* animate */, true /* announce */); + if (msg != 0) { + mMessageAreaController.setMessage(msg); + } + } + + + void reset() { + mPinText = ""; + mPukText = ""; + mState = ENTER_PUK; + handleSubInfoChangeIfNeeded(); + if (mShowDefaultMessage) { + showDefaultMessage(); + } + boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); + + KeyguardEsimArea esimButton = mView.findViewById(R.id.keyguard_esim_area); + esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); + mPasswordEntry.requestFocus(); + } + } + + private void showDefaultMessage() { + if (mRemainingAttempts >= 0) { + mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage( + mRemainingAttempts, true, + KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId))); + return; + } + + boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); + int count = 1; + if (mTelephonyManager != null) { + count = mTelephonyManager.getActiveModemCount(); + } + Resources rez = mView.getResources(); + String msg; + TypedArray array = mView.getContext().obtainStyledAttributes( + new int[] { R.attr.wallpaperTextColor }); + int color = array.getColor(0, Color.WHITE); + array.recycle(); + if (count < 2) { + msg = rez.getString(R.string.kg_puk_enter_puk_hint); + } else { + SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class) + .getSubscriptionInfoForSubId(mSubId); + CharSequence displayName = info != null ? info.getDisplayName() : ""; + msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); + if (info != null) { + color = info.getIconTint(); + } + } + if (isEsimLocked) { + msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); + } + mMessageAreaController.setMessage(msg); + mSimImageView.setImageTintList(ColorStateList.valueOf(color)); + + // Sending empty PUK here to query the number of remaining PIN attempts + new CheckSimPuk("", "", mSubId) { + void onSimLockChangedResponse(final PinResult result) { + if (result == null) Log.e(TAG, "onSimCheckResponse, pin result is NULL"); + else { + Log.d(TAG, "onSimCheckResponse " + " empty One result " + + result.toString()); + if (result.getAttemptsRemaining() >= 0) { + mRemainingAttempts = result.getAttemptsRemaining(); + mMessageAreaController.setMessage( + mView.getPukPasswordErrorMessage( + result.getAttemptsRemaining(), true, + KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId))); + } + } + } + }.start(); + } + + private boolean checkPuk() { + // make sure the puk is at least 8 digits long. + if (mPasswordEntry.getText().length() == 8) { + mPukText = mPasswordEntry.getText(); + return true; + } + return false; + } + + private boolean checkPin() { + // make sure the PIN is between 4 and 8 digits + int length = mPasswordEntry.getText().length(); + if (length >= 4 && length <= 8) { + mPinText = mPasswordEntry.getText(); + return true; + } + return false; + } + + public boolean confirmPin() { + return mPinText.equals(mPasswordEntry.getText()); + } + + + + + private void updateSim() { + getSimUnlockProgressDialog().show(); + + if (mCheckSimPukThread == null) { + mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) { + @Override + void onSimLockChangedResponse(final PinResult result) { + mView.post(() -> { + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } + mView.resetPasswordText(true /* animate */, + /* announce */ + result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); + if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { + mKeyguardUpdateMonitor.reportSimUnlocked(mSubId); + mRemainingAttempts = -1; + mShowDefaultMessage = true; + + getKeyguardSecurityCallback().dismiss( + true, KeyguardUpdateMonitor.getCurrentUser()); + } else { + mShowDefaultMessage = false; + if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { + // show message + mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage( + result.getAttemptsRemaining(), false, + KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId))); + if (result.getAttemptsRemaining() <= 2) { + // this is getting critical - show dialog + getPukRemainingAttemptsDialog( + result.getAttemptsRemaining()).show(); + } else { + // show message + mMessageAreaController.setMessage( + mView.getPukPasswordErrorMessage( + result.getAttemptsRemaining(), false, + KeyguardEsimArea.isEsimLocked( + mView.getContext(), mSubId))); + } + } else { + mMessageAreaController.setMessage(mView.getResources().getString( + R.string.kg_password_puk_failed)); + } + if (DEBUG) { + Log.d(TAG, "verifyPasswordAndUnlock " + + " UpdateSim.onSimCheckResponse: " + + " attemptsRemaining=" + result.getAttemptsRemaining()); + } + } + mStateMachine.reset(); + mCheckSimPukThread = null; + }); + } + }; + mCheckSimPukThread.start(); + } + } + + @Override + protected boolean shouldLockout(long deadline) { + // SIM PUK doesn't have a timed lockout + return false; + } + + private Dialog getSimUnlockProgressDialog() { + if (mSimUnlockProgressDialog == null) { + mSimUnlockProgressDialog = new ProgressDialog(mView.getContext()); + mSimUnlockProgressDialog.setMessage( + mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message)); + mSimUnlockProgressDialog.setIndeterminate(true); + mSimUnlockProgressDialog.setCancelable(false); + if (!(mView.getContext() instanceof Activity)) { + mSimUnlockProgressDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + } + return mSimUnlockProgressDialog; + } + + private void handleSubInfoChangeIfNeeded() { + int subId = mKeyguardUpdateMonitor.getNextSubIdForState( + TelephonyManager.SIM_STATE_PUK_REQUIRED); + if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { + mSubId = subId; + mShowDefaultMessage = true; + mRemainingAttempts = -1; + } + } + + + private Dialog getPukRemainingAttemptsDialog(int remaining) { + String msg = mView.getPukPasswordErrorMessage(remaining, false, + KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)); + if (mRemainingAttemptsDialog == null) { + AlertDialog.Builder builder = new AlertDialog.Builder(mView.getContext()); + builder.setMessage(msg); + builder.setCancelable(false); + builder.setNeutralButton(R.string.ok, null); + mRemainingAttemptsDialog = builder.create(); + mRemainingAttemptsDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } else { + mRemainingAttemptsDialog.setMessage(msg); + } + return mRemainingAttemptsDialog; + } + + @Override + public void onPause() { + // dismiss the dialog. + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.dismiss(); + mSimUnlockProgressDialog = null; + } + } + + /** + * Since the IPC can block, we want to run the request in a separate thread + * with a callback. + */ + private abstract class CheckSimPuk extends Thread { + + private final String mPin, mPuk; + private final int mSubId; + + protected CheckSimPuk(String puk, String pin, int subId) { + mPuk = puk; + mPin = pin; + mSubId = subId; + } + + abstract void onSimLockChangedResponse(@NonNull PinResult result); + + @Override + public void run() { + if (DEBUG) Log.v(TAG, "call supplyPukReportResult()"); + TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId); + final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin); + if (result == null) { + Log.e(TAG, "Error result for supplyPukReportResult."); + mView.post(() -> onSimLockChangedResponse(PinResult.getDefaultFailedResult())); + } else { + if (DEBUG) { + Log.v(TAG, "supplyPukReportResult returned: " + result.toString()); + } + mView.post(new Runnable() { + @Override + public void run() { + onSimLockChangedResponse(result); + } + }); + } + } + } + +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 76090f87223e..bdb34bb56c57 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -56,7 +56,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.face.FaceManager; -import android.hardware.face.FaceSensorProperties; +import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; @@ -1336,7 +1336,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private CancellationSignal mFaceCancelSignal; private FingerprintManager mFpm; private FaceManager mFaceManager; - private List<FaceSensorProperties> mFaceSensorProperties; + private List<FaceSensorPropertiesInternal> mFaceSensorProperties; private boolean mFingerprintLockedOut; private TelephonyManager mTelephonyManager; @@ -1778,7 +1778,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) { mFaceManager = (FaceManager) context.getSystemService(Context.FACE_SERVICE); - mFaceSensorProperties = mFaceManager.getSensorProperties(); + mFaceSensorProperties = mFaceManager.getSensorPropertiesInternal(); } if (mFpm != null || mFaceManager != null) { diff --git a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java index e59602b1cfff..425e50ed6397 100644 --- a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java +++ b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java @@ -16,11 +16,12 @@ package com.android.keyguard; -import android.content.Context; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityManager; +import javax.inject.Inject; + /** * Hover listener that implements lift-to-activate interaction for * accessibility. May be added to multiple views. @@ -31,9 +32,9 @@ class LiftToActivateListener implements View.OnHoverListener { private boolean mCachedClickableState; - public LiftToActivateListener(Context context) { - mAccessibilityManager = (AccessibilityManager) context.getSystemService( - Context.ACCESSIBILITY_SERVICE); + @Inject + LiftToActivateListener(AccessibilityManager accessibilityManager) { + mAccessibilityManager = accessibilityManager; } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index b0457fce6a1a..2205fdd4267d 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -26,6 +26,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityManager; import android.widget.TextView; import com.android.internal.widget.LockPatternUtils; @@ -90,7 +91,8 @@ public class NumPadKey extends ViewGroup { } setOnClickListener(mListener); - setOnHoverListener(new LiftToActivateListener(context)); + setOnHoverListener(new LiftToActivateListener( + (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE))); mLockPatternUtils = new LockPatternUtils(context); mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java index b6010c8915e7..881108858b51 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java @@ -22,6 +22,7 @@ import android.view.ViewGroup; import com.android.keyguard.KeyguardHostView; import com.android.keyguard.KeyguardMessageArea; import com.android.keyguard.KeyguardSecurityContainer; +import com.android.keyguard.KeyguardSecurityViewFlipper; import com.android.systemui.R; import com.android.systemui.statusbar.phone.KeyguardBouncer; @@ -58,7 +59,15 @@ public interface KeyguardBouncerModule { /** */ @Provides @KeyguardBouncerScope - static KeyguardSecurityContainer preovidesKeyguardSecurityContainer(KeyguardHostView hostView) { + static KeyguardSecurityContainer providesKeyguardSecurityContainer(KeyguardHostView hostView) { return hostView.findViewById(R.id.keyguard_security_container); } + + /** */ + @Provides + @KeyguardBouncerScope + static KeyguardSecurityViewFlipper providesKeyguardSecurityViewFlipper( + KeyguardSecurityContainer containerView) { + return containerView.findViewById(R.id.view_flipper); + } } diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index f4c865e1d131..4657b062fb55 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -75,8 +75,10 @@ public final class Prefs { Key.HAS_SEEN_BUBBLES_EDUCATION, Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION, Key.HAS_SEEN_REVERSE_BOTTOM_SHEET, - Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT + Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT, + Key.HAS_SEEN_PRIORITY_ONBOARDING }) + // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them public @interface Key { @Deprecated String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime"; diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 1ebe64860a98..c4a305ec41e2 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -86,6 +86,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.SecureSetting; +import com.android.systemui.settings.UserTracker; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -121,6 +122,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { private final TunerService mTunerService; private DisplayManager.DisplayListener mDisplayListener; private CameraAvailabilityListener mCameraListener; + private final UserTracker mUserTracker; //TODO: These are piecemeal being updated to Points for now to support non-square rounded // corners. for now it is only supposed when reading the intrinsic size from the drawables with @@ -198,11 +200,13 @@ public class ScreenDecorations extends SystemUI implements Tunable { public ScreenDecorations(Context context, @Main Handler handler, BroadcastDispatcher broadcastDispatcher, - TunerService tunerService) { + TunerService tunerService, + UserTracker userTracker) { super(context); mMainHandler = handler; mBroadcastDispatcher = broadcastDispatcher; mTunerService = tunerService; + mUserTracker = userTracker; } @Override @@ -306,7 +310,8 @@ public class ScreenDecorations extends SystemUI implements Tunable { // Watch color inversion and invert the overlay as needed. if (mColorInversionSetting == null) { mColorInversionSetting = new SecureSetting(mContext, mHandler, - Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) { + Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, + mUserTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { updateColorInversion(value); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 714095631fdb..3832ff307d20 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -30,9 +30,11 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; +import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.util.Log; +import android.util.Range; import android.view.Choreographer; import android.view.Display; import android.view.Gravity; @@ -47,12 +49,17 @@ import android.view.SurfaceView; import android.view.View; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.R; import com.android.systemui.shared.system.WindowManagerWrapper; +import java.text.NumberFormat; +import java.util.Locale; + /** * Class to handle adding and removing a window magnification. */ @@ -60,6 +67,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold MirrorWindowControl.MirrorWindowDelegate { private static final String TAG = "WindowMagnificationController"; + // Delay to avoid updating state description too frequently. + private static final int UPDATE_STATE_DESCRIPTION_DELAY_MS = 100; + // It should be consistent with the value defined in WindowMagnificationGestureHandler. + private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(2.0f, 8.0f); + private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f; private final Context mContext; private final Resources mResources; private final Handler mHandler; @@ -95,6 +107,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private final View.OnLayoutChangeListener mMirrorViewLayoutChangeListener; private final View.OnLayoutChangeListener mMirrorSurfaceViewLayoutChangeListener; private final Runnable mMirrorViewRunnable; + private final Runnable mUpdateStateDescriptionRunnable; private View mMirrorView; private SurfaceView mMirrorSurfaceView; private int mMirrorSurfaceMargin; @@ -106,6 +119,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; private Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback; + private Locale mLocale; + private NumberFormat mPercentFormat; @Nullable private MirrorWindowControl mMirrorWindowControl; @@ -164,6 +179,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds); } }; + mUpdateStateDescriptionRunnable = () -> { + if (isWindowVisible()) { + mMirrorView.setStateDescription(formatStateDescription(mScale)); + } + }; } private void updateDimensions() { @@ -292,12 +312,13 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); mMirrorView.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener); + mMirrorView.setAccessibilityDelegate(new MirrorWindowA11yDelegate()); + mWm.addView(mMirrorView, params); SurfaceHolder holder = mMirrorSurfaceView.getHolder(); holder.addCallback(this); holder.setFormat(PixelFormat.RGBA_8888); - addDragTouchListeners(); } @@ -526,6 +547,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold final float offsetY = Float.isNaN(centerY) ? 0 : centerY - mMagnificationFrame.exactCenterY(); mScale = Float.isNaN(scale) ? mScale : scale; + setMagnificationFrameBoundary(); updateMagnificationFramePosition((int) offsetX, (int) offsetY); if (!isWindowVisible()) { @@ -546,6 +568,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold return; } enableWindowMagnification(scale, Float.NaN, Float.NaN); + mHandler.removeCallbacks(mUpdateStateDescriptionRunnable); + mHandler.postDelayed(mUpdateStateDescriptionRunnable, UPDATE_STATE_DESCRIPTION_DELAY_MS); } /** @@ -596,4 +620,78 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private boolean isWindowVisible() { return mMirrorView != null; } + + private CharSequence formatStateDescription(float scale) { + // Cache the locale-appropriate NumberFormat. Configuration locale is guaranteed + // non-null, so the first time this is called we will always get the appropriate + // NumberFormat, then never regenerate it unless the locale changes on the fly. + final Locale curLocale = mContext.getResources().getConfiguration().getLocales().get(0); + if (!curLocale.equals(mLocale)) { + mLocale = curLocale; + mPercentFormat = NumberFormat.getPercentInstance(curLocale); + } + return mPercentFormat.format(scale); + } + + private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate { + + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.addAction( + new AccessibilityAction(R.id.accessibility_action_zoom_in, + mContext.getString(R.string.accessibility_control_zoom_in))); + info.addAction(new AccessibilityAction(R.id.accessibility_action_zoom_out, + mContext.getString(R.string.accessibility_control_zoom_out))); + info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up, + mContext.getString(R.string.accessibility_control_move_up))); + info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down, + mContext.getString(R.string.accessibility_control_move_down))); + info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left, + mContext.getString(R.string.accessibility_control_move_left))); + info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right, + mContext.getString(R.string.accessibility_control_move_right))); + + info.setContentDescription(mContext.getString(R.string.magnification_window_title)); + info.setStateDescription(formatStateDescription(getScale())); + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (performA11yAction(action)) { + return true; + } + return super.performAccessibilityAction(host, action, args); + } + + private boolean performA11yAction(int action) { + if (action == R.id.accessibility_action_zoom_in) { + final float scale = mScale + A11Y_CHANGE_SCALE_DIFFERENCE; + setScale(A11Y_ACTION_SCALE_RANGE.clamp(scale)); + return true; + } + if (action == R.id.accessibility_action_zoom_out) { + final float scale = mScale - A11Y_CHANGE_SCALE_DIFFERENCE; + setScale(A11Y_ACTION_SCALE_RANGE.clamp(scale)); + return true; + } + if (action == R.id.accessibility_action_move_up) { + move(0, -mSourceBounds.height()); + return true; + } + if (action == R.id.accessibility_action_move_down) { + move(0, mSourceBounds.height()); + return true; + } + if (action == R.id.accessibility_action_move_left) { + move(-mSourceBounds.width(), 0); + return true; + } + if (action == R.id.accessibility_action_move_right) { + move(mSourceBounds.width(), 0); + return true; + } + return false; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 38a191c8fb06..b1ae56a584d2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -36,7 +36,7 @@ import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; -import android.hardware.fingerprint.FingerprintSensorProperties; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -306,9 +306,9 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) { - final List<FingerprintSensorProperties> fingerprintSensorProperties = - mFingerprintManager.getSensorProperties(); - for (FingerprintSensorProperties props : fingerprintSensorProperties) { + final List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties = + mFingerprintManager.getSensorPropertiesInternal(); + for (FingerprintSensorPropertiesInternal props : fingerprintSensorProperties) { if (props.isAnyUdfpsType()) { mUdfpsController = mUdfpsControllerFactory.get(); break; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index d79c96ea4774..c409d87a098a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -16,6 +16,7 @@ package com.android.systemui.biometrics; +import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.SuppressLint; @@ -25,6 +26,7 @@ import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.graphics.Point; import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.PowerManager; import android.os.UserHandle; @@ -41,6 +43,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.BrightnessSynchronizer; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; @@ -56,7 +59,15 @@ import javax.inject.Inject; /** * Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events, * and coordinates triggering of the high-brightness mode (HBM). + * + * Note that the current architecture is designed so that a single {@link UdfpsController} + * controls/manages all UDFPS sensors. In other words, a single controller is registered with + * {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such + * as {@link FingerprintManager#onFingerDown(int, int, int, float, float)} or + * {@link IUdfpsOverlayController#showUdfpsOverlay(int)}should all have + * {@code sensorId} parameters. */ +@SuppressWarnings("deprecation") class UdfpsController implements DozeReceiver { private static final String TAG = "UdfpsController"; // Gamma approximation for the sRGB color space. @@ -64,6 +75,10 @@ class UdfpsController implements DozeReceiver { private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000; private final FingerprintManager mFingerprintManager; + // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple + // sensors, this, in addition to a lot of the code here, will be updated. + @VisibleForTesting + final int mUdfpsSensorId; private final WindowManager mWindowManager; private final SystemSettings mSystemSettings; private final DelayableExecutor mFgExecutor; @@ -103,17 +118,17 @@ class UdfpsController implements DozeReceiver { public class UdfpsOverlayController extends IUdfpsOverlayController.Stub { @Override - public void showUdfpsOverlay() { + public void showUdfpsOverlay(int sensorId) { UdfpsController.this.setShowOverlay(true); } @Override - public void hideUdfpsOverlay() { + public void hideUdfpsOverlay(int sensorId) { UdfpsController.this.setShowOverlay(false); } @Override - public void setDebugMessage(String message) { + public void setDebugMessage(int sensorId, String message) { mView.setDebugMessage(message); } } @@ -165,6 +180,18 @@ class UdfpsController implements DozeReceiver { mFgExecutor = fgExecutor; mLayoutParams = createLayoutParams(context); + int udfpsSensorId = -1; + for (FingerprintSensorPropertiesInternal props : + mFingerprintManager.getSensorPropertiesInternal()) { + if (props.isAnyUdfpsType()) { + udfpsSensorId = props.sensorId; + break; + } + } + // At least one UDFPS sensor exists + checkArgument(udfpsSensorId != -1); + mUdfpsSensorId = udfpsSensorId; + mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false); mHbmPath = resources.getString(R.string.udfps_hbm_sysfs_path); @@ -347,7 +374,7 @@ class UdfpsController implements DozeReceiver { fw.write(mHbmEnableCommand); fw.close(); } - mFingerprintManager.onFingerDown(x, y, minor, major); + mFingerprintManager.onFingerDown(mUdfpsSensorId, x, y, minor, major); } catch (IOException e) { mView.hideScrimAndDot(); Log.e(TAG, "onFingerDown | failed to enable HBM: " + e.getMessage()); @@ -355,7 +382,7 @@ class UdfpsController implements DozeReceiver { } private void onFingerUp() { - mFingerprintManager.onFingerUp(); + mFingerprintManager.onFingerUp(mUdfpsSensorId); // Hiding the scrim before disabling HBM results in less noticeable flicker. mView.hideScrimAndDot(); if (mHbmSupported) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index dff405cb162c..90b64eaf5b47 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -88,7 +88,6 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.PinnedStackListenerForwarder; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -112,8 +111,9 @@ import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.util.FloatingContentCoordinator; -import com.android.systemui.wmshell.WindowManagerShellWrapper; +import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.pip.PinnedStackListenerForwarder; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index f2fba23564da..0dcd1d2b4b09 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -16,6 +16,13 @@ package com.android.systemui.bubbles; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + +import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW; +import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; +import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -78,10 +85,10 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; -import com.android.systemui.util.FloatingContentCoordinator; import com.android.systemui.util.RelativeTouchListener; -import com.android.systemui.util.animation.PhysicsAnimator; -import com.android.systemui.util.magnetictarget.MagnetizedObject; +import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -92,12 +99,6 @@ import java.util.Collections; import java.util.List; import java.util.function.Consumer; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW; -import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; -import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; - /** * Renders bubbles in a stack and handles animating expanded and collapsed states. */ diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt index 71faf4a2eeb7..b3c552d24dcd 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt @@ -10,8 +10,8 @@ import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW import com.android.systemui.R -import com.android.systemui.util.DismissCircleView -import com.android.systemui.util.animation.PhysicsAnimator +import com.android.wm.shell.common.DismissCircleView +import com.android.wm.shell.animation.PhysicsAnimator /* * View that handles interactions between DismissCircleView and BubbleStackView. @@ -29,7 +29,7 @@ class DismissView(context: Context) : FrameLayout(context) { var isShowing = false private val animator = PhysicsAnimator.getInstance(circle) - private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY); + private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY) private val DISMISS_SCRIM_FADE_MS = 200 init { setLayoutParams(LayoutParams( diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index f2a4f159f959..9f88ee55082d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -32,8 +32,8 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.util.animation.PhysicsAnimator; -import com.android.systemui.util.magnetictarget.MagnetizedObject; +import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.google.android.collect.Sets; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java index e835ea206e59..b7490a52f475 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -36,9 +36,9 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleStackView; -import com.android.systemui.util.FloatingContentCoordinator; -import com.android.systemui.util.animation.PhysicsAnimator; -import com.android.systemui.util.magnetictarget.MagnetizedObject; +import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.google.android.collect.Sets; @@ -74,7 +74,7 @@ public class StackAnimationController extends */ public static final int DEFAULT_STIFFNESS = 12000; public static final float IME_ANIMATION_STIFFNESS = SpringForce.STIFFNESS_LOW; - private static final int FLING_FOLLOW_STIFFNESS = 20000; + private static final int FLING_FOLLOW_STIFFNESS = 500; public static final float DEFAULT_BOUNCINESS = 0.9f; private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig = diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java index 08902f8c7803..d2eaf0d8d810 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java @@ -40,8 +40,8 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.util.FloatingContentCoordinator; -import com.android.systemui.wmshell.WindowManagerShellWrapper; +import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.common.FloatingContentCoordinator; import dagger.Module; import dagger.Provides; diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt index 658f46e3bb96..2f0fd99337e5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt @@ -16,7 +16,6 @@ package com.android.systemui.controls.controller -import android.app.ActivityManager import android.content.ComponentName import android.content.Context import android.os.IBinder @@ -30,6 +29,7 @@ import android.util.Log import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Lazy import java.util.concurrent.atomic.AtomicBoolean @@ -40,7 +40,8 @@ import javax.inject.Inject open class ControlsBindingControllerImpl @Inject constructor( private val context: Context, @Background private val backgroundExecutor: DelayableExecutor, - private val lazyController: Lazy<ControlsController> + private val lazyController: Lazy<ControlsController>, + userTracker: UserTracker ) : ControlsBindingController { companion object { @@ -56,7 +57,7 @@ open class ControlsBindingControllerImpl @Inject constructor( } } - private var currentUser = UserHandle.of(ActivityManager.getCurrentUser()) + private var currentUser = userTracker.userHandle override val currentUserId: Int get() = currentUser.identifier diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 495872f3433d..d3d24be0ad9d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -16,7 +16,6 @@ package com.android.systemui.controls.controller -import android.app.ActivityManager import android.app.PendingIntent import android.app.backup.BackupManager import android.content.BroadcastReceiver @@ -46,6 +45,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager import com.android.systemui.globalactions.GlobalActionsDialog +import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.DelayableExecutor import java.io.FileDescriptor import java.io.PrintWriter @@ -56,14 +56,15 @@ import javax.inject.Inject @SysUISingleton class ControlsControllerImpl @Inject constructor ( - private val context: Context, - @Background private val executor: DelayableExecutor, - private val uiController: ControlsUiController, - private val bindingController: ControlsBindingController, - private val listingController: ControlsListingController, - private val broadcastDispatcher: BroadcastDispatcher, - optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, - dumpManager: DumpManager + private val context: Context, + @Background private val executor: DelayableExecutor, + private val uiController: ControlsUiController, + private val bindingController: ControlsBindingController, + private val listingController: ControlsListingController, + private val broadcastDispatcher: BroadcastDispatcher, + optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, + dumpManager: DumpManager, + userTracker: UserTracker ) : Dumpable, ControlsController { companion object { @@ -85,7 +86,7 @@ class ControlsControllerImpl @Inject constructor ( private var seedingInProgress = false private val seedingCallbacks = mutableListOf<Consumer<Boolean>>() - private var currentUser = UserHandle.of(ActivityManager.getCurrentUser()) + private var currentUser = userTracker.userHandle override val currentUserId get() = currentUser.identifier diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt index 0d4439fe8ccb..2d76ff2774d6 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt @@ -16,7 +16,6 @@ package com.android.systemui.controls.management -import android.app.ActivityManager import android.content.ComponentName import android.content.Context import android.content.pm.ServiceInfo @@ -29,6 +28,7 @@ import com.android.settingslib.widget.CandidateInfo import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.settings.UserTracker import java.util.concurrent.Executor import java.util.concurrent.atomic.AtomicInteger import javax.inject.Inject @@ -56,14 +56,16 @@ private fun createServiceListing(context: Context): ServiceListing { class ControlsListingControllerImpl @VisibleForTesting constructor( private val context: Context, @Background private val backgroundExecutor: Executor, - private val serviceListingBuilder: (Context) -> ServiceListing + private val serviceListingBuilder: (Context) -> ServiceListing, + userTracker: UserTracker ) : ControlsListingController { @Inject - constructor(context: Context, executor: Executor): this( + constructor(context: Context, executor: Executor, userTracker: UserTracker): this( context, executor, - ::createServiceListing + ::createServiceListing, + userTracker ) private var serviceListing = serviceListingBuilder(context) @@ -78,7 +80,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( private var availableServices = emptyList<ServiceInfo>() private var userChangeInProgress = AtomicInteger(0) - override var currentUserId = ActivityManager.getCurrentUser() + override var currentUserId = userTracker.userId private set private val serviceListingCallback = ServiceListing.Callback { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index c90e6b1360df..e3037543f2e3 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -289,6 +289,7 @@ public class DependencyProvider { /** */ @Provides + @SysUISingleton public LockPatternUtils provideLockPatternUtils(Context context) { return new LockPatternUtils(context); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index b35579d3624b..79925bad3cc7 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -62,6 +62,7 @@ import android.view.ViewConfiguration; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; +import android.view.inputmethod.InputMethodManager; import com.android.internal.app.IBatteryStats; import com.android.internal.statusbar.IStatusBarService; @@ -183,6 +184,12 @@ public class FrameworkServicesModule { @Provides @Singleton + static InputMethodManager provideInputMethodManager(Context context) { + return context.getSystemService(InputMethodManager.class); + } + + @Provides + @Singleton static IPackageManager provideIPackageManager() { return IPackageManager.Stub.asInterface(ServiceManager.getService("package")); } diff --git a/packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java b/packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java new file mode 100644 index 000000000000..dccb24dfe21e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.emergency; + +/** + * Constants for the Emergency gesture. + * + * TODO (b/169175022) Update classname and docs when feature name is locked + */ +public final class EmergencyGesture { + + /** + * Launches the emergency flow. + * + * <p>The emergency flow is triggered by the Emergency gesture. By default the flow will call + * local emergency services, though OEMs can customize the flow. + * + * <p>This action can only be triggered by System UI through the emergency gesture. + * + * <p>TODO (b/169175022) Update action name and docs when feature name is locked + */ + public static final String ACTION_LAUNCH_EMERGENCY = + "com.android.systemui.action.LAUNCH_EMERGENCY"; + + private EmergencyGesture() {} +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index daef2c506ad3..5f726cd1e1f9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -38,6 +38,7 @@ import android.provider.Settings; import android.service.notification.ZenModeConfig; import android.text.TextUtils; import android.text.style.StyleSpan; +import android.util.Log; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; @@ -52,6 +53,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SystemUIAppComponentFactory; +import com.android.systemui.SystemUIFactory; +import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.StatusBarState; @@ -62,6 +65,8 @@ import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.wakelock.SettableWakeLock; import com.android.systemui.util.wakelock.WakeLock; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Date; import java.util.Locale; import java.util.TimeZone; @@ -80,6 +85,8 @@ public class KeyguardSliceProvider extends SliceProvider implements NotificationMediaManager.MediaListener, StatusBarStateController.StateListener, SystemUIAppComponentFactory.ContextInitializer { + private static final String TAG = "KgdSliceProvider"; + private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD); public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main"; private static final String KEYGUARD_HEADER_URI = @@ -310,7 +317,25 @@ public class KeyguardSliceProvider extends SliceProvider implements mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern); mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(getContext(), KeyguardSliceProvider.class), 0); - mMediaManager.addCallback(this); + try { + //TODO(b/168778439): Remove this whole try catch. This is for debugging in dogfood. + mMediaManager.addCallback(this); + } catch (NullPointerException e) { + // We are sometimes failing to set the media manager. Why? + Log.w(TAG, "Failed to setup mMediaManager. Trying again."); + SysUIComponent rootComponent = SystemUIFactory.getInstance().getSysUIComponent(); + try { + Method injectMethod = rootComponent.getClass() + .getMethod("inject", getClass()); + injectMethod.invoke(rootComponent, this); + Log.w("TAG", "mMediaManager is now: " + mMediaManager); + } catch (NoSuchMethodException ex) { + Log.e(TAG, "Failed to find inject method for KeyguardSliceProvider", ex); + } catch (IllegalAccessException | InvocationTargetException ex) { + Log.e(TAG, "Failed to call inject", ex); + } + throw e; + } mStatusBarStateController.addCallback(this); mNextAlarmController.addCallback(this); mZenModeController.addCallback(this); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 9d8e73a0ff47..e50fd6a96b5c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -134,7 +134,7 @@ public class KeyguardModule { // Cameras that support "self illumination," via IR for example, don't need low light // environment mitigation. - boolean needsLowLightMitigation = faceManager.getSensorProperties().stream() + boolean needsLowLightMitigation = faceManager.getSensorPropertiesInternal().stream() .anyMatch((properties) -> !properties.supportsSelfIllumination); if (!needsLowLightMitigation) { return Optional.empty(); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt index 486399979db7..d80aafb714d3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt @@ -32,7 +32,7 @@ import com.android.systemui.qs.PageIndicator import com.android.systemui.R import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS import com.android.systemui.plugins.FalsingManager -import com.android.systemui.util.animation.PhysicsAnimator +import com.android.wm.shell.animation.PhysicsAnimator import com.android.systemui.util.concurrency.DelayableExecutor private const val FLING_SLOP = 1000000 diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 810cecca517f..f6571ef65447 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -251,10 +251,9 @@ public class MediaControlPanel { // App icon ImageView appIcon = mViewHolder.getAppIcon(); if (data.getAppIcon() != null) { - appIcon.setImageDrawable(data.getAppIcon()); + appIcon.setImageIcon(data.getAppIcon()); } else { - Drawable iconDrawable = mContext.getDrawable(R.drawable.ic_music_note); - appIcon.setImageDrawable(iconDrawable); + appIcon.setImageResource(R.drawable.ic_music_note); } // Song name @@ -332,7 +331,7 @@ public class MediaControlPanel { int actionId = ACTION_IDS[i]; final ImageButton button = mViewHolder.getAction(actionId); MediaAction mediaAction = actionIcons.get(i); - button.setImageDrawable(mediaAction.getDrawable()); + button.setImageIcon(mediaAction.getIcon()); button.setContentDescription(mediaAction.getContentDescription()); Runnable action = mediaAction.getAction(); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index 40a879abde34..0ed96eeac402 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -33,7 +33,7 @@ data class MediaData( /** * Icon shown on player, close to app name. */ - val appIcon: Drawable?, + val appIcon: Icon?, /** * Artist name. */ @@ -109,7 +109,7 @@ data class MediaData( /** State of a media action. */ data class MediaAction( - val drawable: Drawable?, + val icon: Icon?, val action: Runnable?, val contentDescription: CharSequence? ) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index cb6b22c2321f..6f6ee4c8091d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -455,7 +455,7 @@ class MediaDataManager( val app = builder.loadHeaderAppName() // App Icon - val smallIconDrawable: Drawable = sbn.notification.smallIcon.loadDrawable(context) + val smallIcon = sbn.notification.smallIcon // Song name var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE) @@ -501,8 +501,13 @@ class MediaDataManager( } else { null } + val mediaActionIcon = if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) { + Icon.createWithResource(packageContext, action.getIcon()!!.getResId()) + } else { + action.getIcon() + } val mediaAction = MediaAction( - action.getIcon().loadDrawable(packageContext), + mediaActionIcon, runnable, action.title) actionIcons.add(mediaAction) @@ -518,7 +523,7 @@ class MediaDataManager( val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true val active = mediaEntries[key]?.active ?: true onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app, - smallIconDrawable, artist, song, artWorkIcon, actionIcons, + smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null, active, resumeAction = resumeAction, isLocalSession = isLocalSession, notificationKey = key, hasCheckedForResume = hasCheckedForResume, @@ -572,7 +577,7 @@ class MediaDataManager( val source = ImageDecoder.createSource(context.getContentResolver(), uri) return try { ImageDecoder.decodeBitmap(source) { - decoder, info, source -> decoder.isMutableRequired = true + decoder, info, source -> decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE } } catch (e: IOException) { Log.e(TAG, "Unable to load bitmap", e) @@ -612,7 +617,7 @@ class MediaDataManager( private fun getResumeMediaAction(action: Runnable): MediaAction { return MediaAction( - context.getDrawable(R.drawable.lb_ic_play), + Icon.createWithResource(context, R.drawable.lb_ic_play), action, context.getString(R.string.controls_media_resume) ) @@ -631,13 +636,13 @@ class MediaDataManager( Assert.isMainThread() val removed = mediaEntries.remove(key) if (useMediaResumption && removed?.resumeAction != null && - !isBlockedFromResume(removed?.packageName)) { + !isBlockedFromResume(removed.packageName)) { Log.d(TAG, "Not removing $key because resumable") // Move to resume key (aka package name) if that key doesn't already exist. val resumeAction = getResumeMediaAction(removed.resumeAction!!) val updated = removed.copy(token = null, actions = listOf(resumeAction), actionsToShowInCompact = listOf(0), active = false, resumption = true) - val pkg = removed?.packageName + val pkg = removed.packageName val migrate = mediaEntries.put(pkg, updated) == null // Notify listeners of "new" controls when migrating or removed and update when not if (migrate) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt index b8872250bb6c..00273bc34552 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt @@ -8,7 +8,7 @@ import android.view.MotionEvent import android.view.ViewGroup import android.widget.HorizontalScrollView import com.android.systemui.Gefingerpoken -import com.android.systemui.util.animation.physicsAnimator +import com.android.wm.shell.animation.physicsAnimator /** * A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 9b6a9ea80ebe..d1630ebe8dc8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -44,6 +44,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { private static final String TAG = "MediaOutputAdapter"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private ViewGroup mConnectedItem; + public MediaOutputAdapter(MediaOutputController controller) { super(controller); } @@ -79,18 +81,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { return mController.getMediaDevices().size(); } - void onItemClick(MediaDevice device) { - mController.connectDevice(device); - device.setState(MediaDeviceState.STATE_CONNECTING); - notifyDataSetChanged(); - } - - void onItemClick(int customizedItem) { - if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) { - mController.launchBluetoothPairing(); - } - } - @Override CharSequence getItemTitle(MediaDevice device) { if (device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE @@ -117,6 +107,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { @Override void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) { super.onBind(device, topMargin, bottomMargin); + final boolean currentlyConnected = isCurrentlyConnected(device); + if (currentlyConnected) { + mConnectedItem = mFrameLayout; + } if (mController.isTransferring()) { if (device.getState() == MediaDeviceState.STATE_CONNECTING && !mController.hasAdjustVolumeUserRestriction()) { @@ -133,16 +127,16 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { false /* showSeekBar*/, false /* showProgressBar */, true /* showSubtitle */); mSubTitleText.setText(R.string.media_output_dialog_connect_failed); - mFrameLayout.setOnClickListener(v -> onItemClick(device)); + mFrameLayout.setOnClickListener(v -> onItemClick(v, device)); } else if (!mController.hasAdjustVolumeUserRestriction() - && isCurrentConnected(device)) { + && currentlyConnected) { setTwoLineLayout(device, null /* title */, true /* bFocused */, true /* showSeekBar*/, false /* showProgressBar */, false /* showSubtitle */); initSeekbar(device); } else { setSingleLineLayout(getItemTitle(device), false /* bFocused */); - mFrameLayout.setOnClickListener(v -> onItemClick(device)); + mFrameLayout.setOnClickListener(v -> onItemClick(v, device)); } } } @@ -160,5 +154,24 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mFrameLayout.setOnClickListener(v -> onItemClick(CUSTOMIZED_ITEM_PAIR_NEW)); } } + + private void onItemClick(View view, MediaDevice device) { + if (mController.isTransferring()) { + return; + } + + playSwitchingAnim(mConnectedItem, view); + mController.connectDevice(device); + device.setState(MediaDeviceState.STATE_CONNECTING); + if (!isAnimating()) { + notifyDataSetChanged(); + } + } + + private void onItemClick(int customizedItem) { + if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) { + mController.launchBluetoothPairing(); + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 01dc6c4b71da..2d3e77db1ea3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -16,6 +16,8 @@ package com.android.systemui.media.dialog; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.graphics.Typeface; import android.text.TextUtils; @@ -33,6 +35,7 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.android.settingslib.media.MediaDevice; +import com.android.systemui.Interpolators; import com.android.systemui.R; /** @@ -50,6 +53,7 @@ public abstract class MediaOutputBaseAdapter extends private boolean mIsDragging; private int mMargin; + private boolean mIsAnimating; Context mContext; View mHolderView; @@ -75,7 +79,7 @@ public abstract class MediaOutputBaseAdapter extends return device.getName(); } - boolean isCurrentConnected(MediaDevice device) { + boolean isCurrentlyConnected(MediaDevice device) { return TextUtils.equals(device.getId(), mController.getCurrentConnectedMediaDevice().getId()); } @@ -84,10 +88,17 @@ public abstract class MediaOutputBaseAdapter extends return mIsDragging; } + boolean isAnimating() { + return mIsAnimating; + } + /** * ViewHolder for binding device view. */ abstract class MediaDeviceBaseViewHolder extends RecyclerView.ViewHolder { + + private static final int ANIM_DURATION = 200; + final FrameLayout mFrameLayout; final TextView mTitleText; final TextView mTwoLineTitleText; @@ -123,17 +134,16 @@ public abstract class MediaOutputBaseAdapter extends private void setMargin(boolean topMargin, boolean bottomMargin) { ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mFrameLayout .getLayoutParams(); - if (topMargin) { - params.topMargin = mMargin; - } - if (bottomMargin) { - params.bottomMargin = mMargin; - } + params.topMargin = topMargin ? mMargin : 0; + params.bottomMargin = bottomMargin ? mMargin : 0; mFrameLayout.setLayoutParams(params); } + void setSingleLineLayout(CharSequence title, boolean bFocused) { - mTitleText.setVisibility(View.VISIBLE); mTwoLineLayout.setVisibility(View.GONE); + mProgressBar.setVisibility(View.GONE); + mTitleText.setVisibility(View.VISIBLE); + mTitleText.setTranslationY(0); mTitleText.setText(title); if (bFocused) { mTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE, Typeface.NORMAL)); @@ -146,9 +156,11 @@ public abstract class MediaOutputBaseAdapter extends boolean showSeekBar, boolean showProgressBar, boolean showSubtitle) { mTitleText.setVisibility(View.GONE); mTwoLineLayout.setVisibility(View.VISIBLE); + mSeekBar.setAlpha(1); mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE); mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE); + mTwoLineTitleText.setTranslationY(0); if (device == null) { mTwoLineTitleText.setText(title); } else { @@ -189,5 +201,53 @@ public abstract class MediaOutputBaseAdapter extends } }); } + + void playSwitchingAnim(@NonNull View from, @NonNull View to) { + final float delta = (float) (mContext.getResources().getDimensionPixelSize( + R.dimen.media_output_dialog_title_anim_y_delta)); + final SeekBar fromSeekBar = from.requireViewById(R.id.volume_seekbar); + final TextView toTitleText = to.requireViewById(R.id.title); + if (fromSeekBar.getVisibility() != View.VISIBLE || toTitleText.getVisibility() + != View.VISIBLE) { + return; + } + mIsAnimating = true; + // Animation for title text + toTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE, Typeface.NORMAL)); + toTitleText.animate() + .setDuration(ANIM_DURATION) + .translationY(-delta) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + to.requireViewById(R.id.volume_indeterminate_progress).setVisibility( + View.VISIBLE); + } + }); + // Animation for seek bar + fromSeekBar.animate() + .alpha(0) + .setDuration(ANIM_DURATION) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + final TextView fromTitleText = from.requireViewById( + R.id.two_line_title); + fromTitleText.setTypeface(Typeface.create(FONT_TITLE, Typeface.NORMAL)); + fromTitleText.animate() + .setDuration(ANIM_DURATION) + .translationY(delta) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mIsAnimating = false; + notifyDataSetChanged(); + } + }); + } + }); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index ebca8a735ad5..3b8299913e5b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -170,7 +170,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mHeaderSubtitle.setText(subTitle); mHeaderTitle.setGravity(Gravity.NO_GRAVITY); } - if (!mAdapter.isDragging()) { + if (!mAdapter.isDragging() && !mAdapter.isAnimating()) { mAdapter.notifyDataSetChanged(); } // Show when remote media session is available diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 6d6d6cbfc780..1538e9ea61e1 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -207,6 +207,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa private boolean mUseMLModel; private float mMLModelThreshold; private String mPackageName; + private float mMLResults; private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; @@ -531,10 +532,10 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa new long[]{(long) y}, }; - final float results = mBackGestureTfClassifierProvider.predict(featuresVector); - if (results == -1) return -1; + mMLResults = mBackGestureTfClassifierProvider.predict(featuresVector); + if (mMLResults == -1) return -1; - return results >= mMLModelThreshold ? 1 : 0; + return mMLResults >= mMLModelThreshold ? 1 : 0; } private boolean isWithinTouchRegion(int x, int y) { @@ -611,7 +612,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa (int) mDownPoint.x, (int) mDownPoint.y, (int) mEndPoint.x, (int) mEndPoint.y, mEdgeWidthLeft + mLeftInset, - mDisplaySize.x - (mEdgeWidthRight + mRightInset)); + mDisplaySize.x - (mEdgeWidthRight + mRightInset), + mUseMLModel ? mMLResults : -2); } private void onMotionEvent(MotionEvent ev) { @@ -621,6 +623,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa // either the bouncer is showing or the notification panel is hidden mInputEventReceiver.setBatchingEnabled(false); mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset; + mMLResults = 0; mLogGesture = false; mInRejectedExclusion = false; mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java b/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java deleted file mode 100644 index 8e8b7f37b8d6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.pip.tv.dagger; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import com.android.systemui.pip.tv.PipControlsView; -import com.android.systemui.pip.tv.PipControlsViewController; -import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import javax.inject.Scope; - -import dagger.BindsInstance; -import dagger.Subcomponent; - -/** - * Component for injecting into Pip related classes. - */ -@Subcomponent -public interface TvPipComponent { - /** - * Builder for {@link StatusBarComponent}. - */ - @Subcomponent.Builder - interface Builder { - @BindsInstance - TvPipComponent.Builder pipControlsView(PipControlsView pipControlsView); - TvPipComponent build(); - } - - /** - * Scope annotation for singleton items within the PipComponent. - */ - @Documented - @Retention(RUNTIME) - @Scope - @interface PipScope {} - - /** - * Creates a StatusBarWindowViewController. - */ - @TvPipComponent.PipScope - PipControlsViewController getPipControlsViewController(); -} diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index 0fbd73b615ce..f56e6cdf5cb7 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -16,25 +16,23 @@ package com.android.systemui.privacy -import android.app.ActivityManager import android.app.AppOpsManager -import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.UserInfo import android.os.UserHandle -import android.os.UserManager import android.provider.DeviceConfig import com.android.internal.annotations.VisibleForTesting import com.android.internal.config.sysui.SystemUiDeviceConfigFlags import com.android.systemui.Dumpable import com.android.systemui.appops.AppOpItem import com.android.systemui.appops.AppOpsController -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.settings.UserTracker import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.concurrency.DelayableExecutor import java.io.FileDescriptor @@ -48,9 +46,8 @@ class PrivacyItemController @Inject constructor( private val appOpsController: AppOpsController, @Main uiExecutor: DelayableExecutor, @Background private val bgExecutor: Executor, - private val broadcastDispatcher: BroadcastDispatcher, private val deviceConfigProxy: DeviceConfigProxy, - private val userManager: UserManager, + private val userTracker: UserTracker, dumpManager: DumpManager ) : Dumpable { @@ -153,13 +150,16 @@ class PrivacyItemController @Inject constructor( } @VisibleForTesting - internal var userSwitcherReceiver = Receiver() - set(value) { - unregisterReceiver() - field = value - if (listening) registerReceiver() + internal var userTrackerCallback = object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + update(true) } + override fun onProfilesChanged(profiles: List<UserInfo>) { + update(true) + } + } + init { deviceConfigProxy.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_PRIVACY, @@ -168,20 +168,18 @@ class PrivacyItemController @Inject constructor( dumpManager.registerDumpable(TAG, this) } - private fun unregisterReceiver() { - broadcastDispatcher.unregisterReceiver(userSwitcherReceiver) + private fun unregisterListener() { + userTracker.removeCallback(userTrackerCallback) } private fun registerReceiver() { - broadcastDispatcher.registerReceiver(userSwitcherReceiver, intentFilter, - null /* handler */, UserHandle.ALL) + userTracker.addCallback(userTrackerCallback, bgExecutor) } private fun update(updateUsers: Boolean) { bgExecutor.execute { if (updateUsers) { - val currentUser = ActivityManager.getCurrentUser() - currentUserIds = userManager.getProfiles(currentUser).map { it.id } + currentUserIds = userTracker.userProfiles.map { it.id } } updateListAndNotifyChanges.run() } @@ -206,7 +204,7 @@ class PrivacyItemController @Inject constructor( update(true) } else { appOpsController.removeCallback(OPS, cb) - unregisterReceiver() + unregisterListener() // Make sure that we remove all indicators and notify listeners if we are not // listening anymore due to indicators being disabled update(false) @@ -275,14 +273,6 @@ class PrivacyItemController @Inject constructor( fun onFlagMicCameraChanged(flag: Boolean) {} } - internal inner class Receiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (intentFilter.hasAction(intent.action)) { - update(true) - } - } - } - private class NotifyChangesToCallback( private val callback: Callback?, private val list: List<PrivacyItem> diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java index 52a2cecec6b1..0053fea35262 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java @@ -2,6 +2,7 @@ package com.android.systemui.qs; import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.drawable.Animatable2; import android.graphics.drawable.AnimatedVectorDrawable; @@ -31,9 +32,6 @@ public class PageIndicator extends ViewGroup { private static final long ANIMATION_DURATION = 250; - // The size of a single dot in relation to the whole animation. - private static final float SINGLE_SCALE = .4f; - private static final float MINOR_ALPHA = .42f; private final ArrayList<Integer> mQueuedPositions = new ArrayList<>(); @@ -75,11 +73,10 @@ public class PageIndicator extends ViewGroup { } array.recycle(); - mPageIndicatorWidth = - (int) mContext.getResources().getDimension(R.dimen.qs_page_indicator_width); - mPageIndicatorHeight = - (int) mContext.getResources().getDimension(R.dimen.qs_page_indicator_height); - mPageDotWidth = (int) (mPageIndicatorWidth * SINGLE_SCALE); + Resources res = context.getResources(); + mPageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width); + mPageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height); + mPageDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width); } public void setNumPages(int numPages) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 22c735d5fa11..04f379ef35ea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -177,6 +177,16 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } @Override + public void endFakeDrag() { + try { + super.endFakeDrag(); + } catch (NullPointerException e) { + // Not sure what's going on. Let's log it + Log.e(TAG, "endFakeDrag called without velocityTracker", e); + } + } + + @Override public void computeScroll() { if (!mScroller.isFinished() && mScroller.computeScrollOffset()) { if (!isFakeDragging()) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index eba4465018ab..ac55fa0cf838 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -31,7 +31,7 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.R; import com.android.systemui.qs.customize.QSCustomizer; -import com.android.systemui.util.animation.PhysicsAnimator; +import com.android.wm.shell.animation.PhysicsAnimator; /** * Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index 6e4ab9a3323a..84563a078447 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -20,6 +20,8 @@ import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -34,6 +36,7 @@ import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.View.OnClickListener; @@ -57,6 +60,7 @@ import com.android.systemui.R; import com.android.systemui.R.dimen; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.TouchAnimator.Builder; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.MultiUserSwitch; import com.android.systemui.statusbar.phone.SettingsButton; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -75,6 +79,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, private final ActivityStarter mActivityStarter; private final UserInfoController mUserInfoController; private final DeviceProvisionedController mDeviceProvisionedController; + private final UserTracker mUserTracker; private SettingsButton mSettingsButton; protected View mSettingsContainer; private PageIndicator mPageIndicator; @@ -115,11 +120,12 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, @Inject public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, ActivityStarter activityStarter, UserInfoController userInfoController, - DeviceProvisionedController deviceProvisionedController) { + DeviceProvisionedController deviceProvisionedController, UserTracker userTracker) { super(context, attrs); mActivityStarter = activityStarter; mUserInfoController = userInfoController; mDeviceProvisionedController = deviceProvisionedController; + mUserTracker = userTracker; } @VisibleForTesting @@ -127,7 +133,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, this(context, attrs, Dependency.get(ActivityStarter.class), Dependency.get(UserInfoController.class), - Dependency.get(DeviceProvisionedController.class)); + Dependency.get(DeviceProvisionedController.class), + Dependency.get(UserTracker.class)); } @Override @@ -150,6 +157,19 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, mActionsContainer = findViewById(R.id.qs_footer_actions_container); mEditContainer = findViewById(R.id.qs_footer_actions_edit_container); mBuildText = findViewById(R.id.build); + mBuildText.setOnLongClickListener(view -> { + CharSequence buildText = mBuildText.getText(); + if (!TextUtils.isEmpty(buildText)) { + ClipboardManager service = + mUserTracker.getUserContext().getSystemService(ClipboardManager.class); + String label = mContext.getString(R.string.build_number_clip_data_label); + service.setPrimaryClip(ClipData.newPlainText(label, buildText)); + Toast.makeText(mContext, R.string.build_number_copy_toast, Toast.LENGTH_SHORT) + .show(); + return true; + } + return false; + }); // RenderThread is doing more harm than good when touching the header (to expand quick // settings), so disable it for this view @@ -176,6 +196,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, mBuildText.setSelected(true); mShouldShowBuildText = true; } else { + mBuildText.setText(null); mShouldShowBuildText = false; mBuildText.setSelected(false); } @@ -317,12 +338,14 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE); mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE); mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE); + mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE); } private void updateVisibilities() { mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE); mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility( - TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE); + TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle()) ? View.VISIBLE + : View.INVISIBLE); final boolean isDemo = UserManager.isDeviceInDemoMode(mContext); mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE); mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE); @@ -376,15 +399,16 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH); if (mSettingsButton.isTunerClick()) { mActivityStarter.postQSRunnableDismissingKeyguard(() -> { - if (TunerService.isTunerEnabled(mContext)) { - TunerService.showResetRequest(mContext, () -> { - // Relaunch settings so that the tuner disappears. - startSettingsActivity(); - }); + if (TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle())) { + TunerService.showResetRequest(mContext, mUserTracker.getUserHandle(), + () -> { + // Relaunch settings so that the tuner disappears. + startSettingsActivity(); + }); } else { Toast.makeText(getContext(), R.string.tuner_toast, Toast.LENGTH_LONG).show(); - TunerService.setTunerEnabled(mContext, true); + TunerService.setTunerEnabled(mContext, mUserTracker.getUserHandle(), true); } startSettingsActivity(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java index 290ab8594fc0..000fd1c4bd2e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java @@ -30,6 +30,7 @@ public interface QSHost { void openPanels(); Context getContext(); Context getUserContext(); + int getUserId(); UiEventLogger getUiEventLogger(); Collection<QSTile> getTiles(); void addCallback(Callback callback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index ae925d1d6ee6..fdc0a60e4475 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -25,7 +25,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.PointF; import android.metrics.LogMaker; import android.os.Bundle; import android.os.Handler; @@ -57,6 +56,7 @@ import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.BrightnessController; import com.android.systemui.settings.ToggleSliderView; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener; import com.android.systemui.tuner.TunerService; @@ -114,6 +114,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private final QSLogger mQSLogger; protected final UiEventLogger mUiEventLogger; protected QSTileHost mHost; + private final UserTracker mUserTracker; @Nullable protected QSSecurityFooter mSecurityFooter; @@ -157,7 +158,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne BroadcastDispatcher broadcastDispatcher, QSLogger qsLogger, MediaHost mediaHost, - UiEventLogger uiEventLogger + UiEventLogger uiEventLogger, + UserTracker userTracker ) { super(context, attrs); mUsingMediaPlayer = useQsMediaPlayer(context); @@ -173,6 +175,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mDumpManager = dumpManager; mBroadcastDispatcher = broadcastDispatcher; mUiEventLogger = uiEventLogger; + mUserTracker = userTracker; setOrientation(VERTICAL); @@ -221,7 +224,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } protected void addSecurityFooter() { - mSecurityFooter = new QSSecurityFooter(this, mContext); + mSecurityFooter = new QSSecurityFooter(this, mContext, mUserTracker); } protected void addViewsAboveTiles() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index afc5be4e6c2f..0891972c11d2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -15,7 +15,6 @@ */ package com.android.systemui.qs; -import android.app.ActivityManager; import android.app.AlertDialog; import android.app.admin.DevicePolicyEventLogger; import android.content.Context; @@ -45,6 +44,7 @@ import com.android.systemui.Dependency; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.SecurityController; @@ -61,8 +61,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic private final SecurityController mSecurityController; private final ActivityStarter mActivityStarter; private final Handler mMainHandler; - - private final UserManager mUm; + private final UserTracker mUserTracker; private AlertDialog mDialog; private QSTileHost mHost; @@ -73,7 +72,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic private int mFooterTextId; private int mFooterIconId; - public QSSecurityFooter(QSPanel qsPanel, Context context) { + public QSSecurityFooter(QSPanel qsPanel, Context context, UserTracker userTracker) { mRootView = LayoutInflater.from(context) .inflate(R.layout.quick_settings_footer, qsPanel, false); mRootView.setOnClickListener(this); @@ -85,7 +84,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic mActivityStarter = Dependency.get(ActivityStarter.class); mSecurityController = Dependency.get(SecurityController.class); mHandler = new H(Dependency.get(Dependency.BG_LOOPER)); - mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mUserTracker = userTracker; } public void setHostEnvironment(QSTileHost host) { @@ -138,7 +137,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic private void handleRefreshState() { final boolean isDeviceManaged = mSecurityController.isDeviceManaged(); - final UserInfo currentUser = mUm.getUserInfo(ActivityManager.getCurrentUser()); + final UserInfo currentUser = mUserTracker.getUserInfo(); final boolean isDemoDevice = UserManager.isDeviceInDemoMode(mContext) && currentUser != null && currentUser.isDemo(); final boolean hasWorkProfile = mSecurityController.hasWorkProfile(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 9a63a56b2c8e..0d0d01249c3d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -14,7 +14,6 @@ package com.android.systemui.qs; -import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -49,6 +48,7 @@ import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.external.TileLifecycleManager; import com.android.systemui.qs.external.TileServices; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.StatusBar; @@ -99,6 +99,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private int mCurrentUser; private final Optional<StatusBar> mStatusBarOptional; private Context mUserContext; + private UserTracker mUserTracker; @Inject public QSTileHost(Context context, @@ -113,7 +114,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D BroadcastDispatcher broadcastDispatcher, Optional<StatusBar> statusBarOptional, QSLogger qsLogger, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + UserTracker userTracker) { mIconController = iconController; mContext = context; mUserContext = context; @@ -125,12 +127,13 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mBroadcastDispatcher = broadcastDispatcher; mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID); - mServices = new TileServices(this, bgLooper, mBroadcastDispatcher); + mServices = new TileServices(this, bgLooper, mBroadcastDispatcher, userTracker); mStatusBarOptional = statusBarOptional; mQsFactories.add(defaultFactory); pluginManager.addPluginListener(this, QSFactory.class, true); mDumpManager.registerDumpable(TAG, this); + mUserTracker = userTracker; mainHandler.post(() -> { // This is technically a hack to avoid circular dependency of @@ -230,6 +233,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } @Override + public int getUserId() { + return mCurrentUser; + } + + @Override public TileServices getTileServices() { return mServices; } @@ -248,9 +256,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode); } final List<String> tileSpecs = loadTileSpecs(mContext, newValue); - int currentUser = ActivityManager.getCurrentUser(); + int currentUser = mUserTracker.getUserId(); if (currentUser != mCurrentUser) { - mUserContext = mContext.createContextAsUser(UserHandle.of(currentUser), 0); + mUserContext = mUserTracker.getUserContext(); if (mAutoTiles != null) { mAutoTiles.changeUser(UserHandle.of(currentUser)); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index affb7b91b6a5..ea036f6fe0e5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -38,6 +38,7 @@ import com.android.systemui.plugins.qs.QSTile.SignalState; import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.qs.customize.QSCustomizer; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.settings.UserTracker; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -70,9 +71,11 @@ public class QuickQSPanel extends QSPanel { BroadcastDispatcher broadcastDispatcher, QSLogger qsLogger, MediaHost mediaHost, - UiEventLogger uiEventLogger + UiEventLogger uiEventLogger, + UserTracker userTracker ) { - super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger); + super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger, + userTracker); sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); applyBottomMargin((View) mRegularTileLayout); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 2e258d56ece0..544249a51b08 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -20,7 +20,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; import android.annotation.ColorInt; -import android.app.ActivityManager; import android.app.AlarmManager; import android.content.Context; import android.content.Intent; @@ -30,6 +29,7 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.media.AudioManager; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.provider.AlarmClock; @@ -64,6 +64,8 @@ import com.android.systemui.BatteryMeterView; import com.android.systemui.DualToneHandler; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.demomode.DemoMode; +import com.android.systemui.demomode.DemoModeController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; @@ -73,6 +75,7 @@ import com.android.systemui.privacy.PrivacyItem; import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.qs.QSDetail.Callback; import com.android.systemui.qs.carrier.QSCarrierGroup; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager; @@ -151,6 +154,9 @@ public class QuickStatusBarHeader extends RelativeLayout implements private Space mSpace; private BatteryMeterView mBatteryRemainingIcon; private RingerModeTracker mRingerModeTracker; + private DemoModeController mDemoModeController; + private DemoMode mDemoModeReceiver; + private UserTracker mUserTracker; private boolean mAllIndicatorsEnabled; private boolean mMicCameraIndicatorsEnabled; @@ -207,7 +213,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements StatusBarIconController statusBarIconController, ActivityStarter activityStarter, PrivacyItemController privacyItemController, CommandQueue commandQueue, RingerModeTracker ringerModeTracker, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, DemoModeController demoModeController, + UserTracker userTracker) { super(context, attrs); mAlarmController = nextAlarmController; mZenController = zenModeController; @@ -219,6 +226,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements mCommandQueue = commandQueue; mRingerModeTracker = ringerModeTracker; mUiEventLogger = uiEventLogger; + mDemoModeController = demoModeController; + mUserTracker = userTracker; } @Override @@ -268,6 +277,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements mClockView = findViewById(R.id.clock); mClockView.setOnClickListener(this); + mDemoModeReceiver = new ClockDemoModeReceiver(mClockView); mDateView = findViewById(R.id.date); mSpace = findViewById(R.id.space); @@ -526,6 +536,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements updateStatusText(); }); mStatusBarIconController.addIconGroup(mIconManager); + mDemoModeController.addCallback(mDemoModeReceiver); requestApplyInsets(); } @@ -606,6 +617,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements setListening(false); mRingerModeTracker.getRingerModeInternal().removeObservers(this); mStatusBarIconController.removeIconGroup(mIconManager); + mDemoModeController.removeCallback(mDemoModeReceiver); super.onDetachedFromWindow(); } @@ -714,7 +726,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements return ""; } String skeleton = android.text.format.DateFormat - .is24HourFormat(mContext, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma"; + .is24HourFormat(mContext, mUserTracker.getUserId()) ? "EHm" : "Ehma"; String pattern = android.text.format.DateFormat .getBestDateTimePattern(Locale.getDefault(), skeleton); return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString(); @@ -769,4 +781,33 @@ public class QuickStatusBarHeader extends RelativeLayout implements private boolean getChipEnabled() { return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled; } + + private static class ClockDemoModeReceiver implements DemoMode { + private Clock mClockView; + + @Override + public List<String> demoCommands() { + return List.of(COMMAND_CLOCK); + } + + ClockDemoModeReceiver(Clock clockView) { + mClockView = clockView; + } + + @Override + public void dispatchDemoCommand(String command, Bundle args) { + mClockView.dispatchDemoCommand(command, args); + } + + @Override + public void onDemoModeStarted() { + mClockView.onDemoModeStarted(); + } + + @Override + public void onDemoModeFinished() { + mClockView.onDemoModeFinished(); + } + } + } diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java index 65d815053e47..3ee3e117fb0f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java +++ b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java @@ -16,7 +16,6 @@ package com.android.systemui.qs; -import android.app.ActivityManager; import android.content.Context; import android.database.ContentObserver; import android.os.Handler; @@ -37,10 +36,6 @@ public abstract class SecureSetting extends ContentObserver implements Listenabl protected abstract void handleValueChanged(int value, boolean observedChange); - protected SecureSetting(Context context, Handler handler, String settingName) { - this(context, handler, settingName, ActivityManager.getCurrentUser()); - } - public SecureSetting(Context context, Handler handler, String settingName, int userId) { super(handler); mContext = context; diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index bffeb3ec3c70..e049025ba9bd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -607,6 +607,12 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final ViewHolder holder = parent.getChildViewHolder(child); + // Do not draw background for the holder that's currently being dragged + if (holder == mCurrentDrag) { + continue; + } + // Do not draw background for holders before the edit index (header and current + // tiles) if (holder.getAdapterPosition() == 0 || holder.getAdapterPosition() < mEditIndex && !(child instanceof TextView)) { continue; diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index 73c6504b9983..b795a5f5ea19 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -17,7 +17,6 @@ package com.android.systemui.qs.customize; import android.Manifest.permission; -import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -40,6 +39,7 @@ import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon; +import com.android.systemui.settings.UserTracker; import com.android.systemui.util.leak.GarbageMonitor; import java.util.ArrayList; @@ -58,16 +58,18 @@ public class TileQueryHelper { private final Executor mMainExecutor; private final Executor mBgExecutor; private final Context mContext; + private final UserTracker mUserTracker; private TileStateListener mListener; private boolean mFinished; @Inject - public TileQueryHelper(Context context, + public TileQueryHelper(Context context, UserTracker userTracker, @Main Executor mainExecutor, @Background Executor bgExecutor) { mContext = context; mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; + mUserTracker = userTracker; } public void setListener(TileStateListener listener) { @@ -207,7 +209,7 @@ public class TileQueryHelper { Collection<QSTile> params = host.getTiles(); PackageManager pm = mContext.getPackageManager(); List<ResolveInfo> services = pm.queryIntentServicesAsUser( - new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser()); + new Intent(TileService.ACTION_QS_TILE), 0, mUserTracker.getUserId()); String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock); for (ResolveInfo info : services) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 19c7b6cefc5d..6e28cd89d43a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -18,7 +18,6 @@ package com.android.systemui.qs.external; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG; -import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -304,8 +303,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener } private Intent resolveIntent(Intent i) { - ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0, - ActivityManager.getCurrentUser()); + ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0, mUser); return result != null ? new Intent(TileService.ACTION_QS_TILE_PREFERENCES) .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index cfa8fb6373a1..7e76e57f4802 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -15,7 +15,6 @@ */ package com.android.systemui.qs.external; -import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -26,7 +25,6 @@ import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Handler; import android.os.IBinder; -import android.os.UserHandle; import android.service.quicksettings.IQSTileService; import android.service.quicksettings.Tile; import android.service.quicksettings.TileService; @@ -36,6 +34,7 @@ import androidx.annotation.VisibleForTesting; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener; +import com.android.systemui.settings.UserTracker; import java.util.List; import java.util.Objects; @@ -60,6 +59,7 @@ public class TileServiceManager { private final TileServices mServices; private final TileLifecycleManager mStateManager; private final Handler mHandler; + private final UserTracker mUserTracker; private boolean mBindRequested; private boolean mBindAllowed; private boolean mBound; @@ -73,25 +73,26 @@ public class TileServiceManager { private boolean mStarted = false; TileServiceManager(TileServices tileServices, Handler handler, ComponentName component, - Tile tile, BroadcastDispatcher broadcastDispatcher) { - this(tileServices, handler, new TileLifecycleManager(handler, + Tile tile, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) { + this(tileServices, handler, userTracker, new TileLifecycleManager(handler, tileServices.getContext(), tileServices, tile, new Intent().setComponent(component), - new UserHandle(ActivityManager.getCurrentUser()), broadcastDispatcher)); + userTracker.getUserHandle(), broadcastDispatcher)); } @VisibleForTesting - TileServiceManager(TileServices tileServices, Handler handler, + TileServiceManager(TileServices tileServices, Handler handler, UserTracker userTracker, TileLifecycleManager tileLifecycleManager) { mServices = tileServices; mHandler = handler; mStateManager = tileLifecycleManager; + mUserTracker = userTracker; IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); Context context = mServices.getContext(); - context.registerReceiverAsUser(mUninstallReceiver, - new UserHandle(ActivityManager.getCurrentUser()), filter, null, mHandler); + context.registerReceiverAsUser(mUninstallReceiver, userTracker.getUserHandle(), filter, + null, mHandler); } boolean isLifecycleStarted() { @@ -279,7 +280,7 @@ public class TileServiceManager { queryIntent.setPackage(pkgName); PackageManager pm = context.getPackageManager(); List<ResolveInfo> services = pm.queryIntentServicesAsUser( - queryIntent, 0, ActivityManager.getCurrentUser()); + queryIntent, 0, mUserTracker.getUserId()); for (ResolveInfo info : services) { if (Objects.equals(info.serviceInfo.packageName, component.getPackageName()) && Objects.equals(info.serviceInfo.name, component.getClassName())) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 2863d08a75dc..35cf2a12745e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -39,6 +39,7 @@ import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.Dependency; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -62,15 +63,18 @@ public class TileServices extends IQSService.Stub { private final Handler mMainHandler; private final QSTileHost mHost; private final BroadcastDispatcher mBroadcastDispatcher; + private final UserTracker mUserTracker; private int mMaxBound = DEFAULT_MAX_BOUND; - public TileServices(QSTileHost host, Looper looper, BroadcastDispatcher broadcastDispatcher) { + public TileServices(QSTileHost host, Looper looper, BroadcastDispatcher broadcastDispatcher, + UserTracker userTracker) { mHost = host; mContext = mHost.getContext(); mBroadcastDispatcher = broadcastDispatcher; mHandler = new Handler(looper); mMainHandler = new Handler(Looper.getMainLooper()); + mUserTracker = userTracker; mBroadcastDispatcher.registerReceiver( mRequestListeningReceiver, new IntentFilter(TileService.ACTION_REQUEST_LISTENING), @@ -104,7 +108,7 @@ public class TileServices extends IQSService.Stub { protected TileServiceManager onCreateTileService(ComponentName component, Tile tile, BroadcastDispatcher broadcastDispatcher) { return new TileServiceManager(this, mHandler, component, tile, - broadcastDispatcher); + broadcastDispatcher, mUserTracker); } public void freeService(CustomTile tile, TileServiceManager service) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index dfd7e2c8fdb7..5a81676567a7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -31,7 +31,6 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.annotation.CallSuper; import android.annotation.NonNull; -import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; @@ -521,9 +520,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) { EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, - userRestriction, ActivityManager.getCurrentUser()); + userRestriction, mHost.getUserId()); if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext, - userRestriction, ActivityManager.getCurrentUser())) { + userRestriction, mHost.getUserId())) { state.disabledByPolicy = true; mEnforcedAdmin = admin; } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index 347ef45824c9..98782f7c8b55 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -38,6 +38,7 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.SecureSetting; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.settings.UserTracker; import javax.inject.Inject; @@ -61,13 +62,14 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, - QSLogger qsLogger + QSLogger qsLogger, + UserTracker userTracker ) { super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, activityStarter, qsLogger); mSetting = new SecureSetting(mContext, mainHandler, - Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) { + Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { // mHandler is the background handler so calling this is OK diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index ec8b1435e201..2076cbffa425 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -19,7 +19,6 @@ package com.android.systemui.qs.tiles; import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_OFF; -import android.app.ActivityManager; import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; @@ -203,7 +202,7 @@ public class DndTile extends QSTileImpl<BooleanState> { break; default: Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration, - ActivityManager.getCurrentUser(), true).id; + mHost.getUserId(), true).id; mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionId, TAG); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java index 47002683c6b9..bbeff6ece902 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java @@ -16,30 +16,17 @@ package com.android.systemui.recents; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; - import android.annotation.Nullable; -import android.app.ActivityManager; import android.app.trust.TrustManager; import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.RemoteException; import android.util.Log; -import android.view.Display; -import android.widget.Toast; import com.android.systemui.Dependency; -import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.shared.recents.IOverviewProxy; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.wm.shell.splitscreen.SplitScreen; import java.util.Optional; @@ -56,7 +43,6 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation { private final static String TAG = "OverviewProxyRecentsImpl"; @Nullable private final Lazy<StatusBar> mStatusBarLazy; - private final Optional<SplitScreen> mSplitScreenOptional; private Context mContext; private Handler mHandler; @@ -65,10 +51,8 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation { @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Inject - public OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy, - Optional<SplitScreen> splitScreenOptional) { + public OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy) { mStatusBarLazy = statusBarLazy.orElse(null); - mSplitScreenOptional = splitScreenOptional; } @Override @@ -140,42 +124,4 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation { // Do nothing } } - - @Override - public boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds, - int metricsDockAction) { - Point realSize = new Point(); - if (initialBounds == null) { - mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY) - .getRealSize(realSize); - initialBounds = new Rect(0, 0, realSize.x, realSize.y); - } - - ActivityManager.RunningTaskInfo runningTask = - ActivityManagerWrapper.getInstance().getRunningTask(); - final int activityType = runningTask != null - ? runningTask.configuration.windowConfiguration.getActivityType() - : ACTIVITY_TYPE_UNDEFINED; - boolean screenPinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive(); - boolean isRunningTaskInHomeOrRecentsStack = - activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS; - if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) { - if (runningTask.supportsSplitScreenMultiWindow) { - if (ActivityManagerWrapper.getInstance().setTaskWindowingModeSplitScreenPrimary( - runningTask.id, stackCreateMode, initialBounds)) { - mSplitScreenOptional.ifPresent(splitScreen -> { - splitScreen.onDockedTopTask(); - // The overview service is handling split screen, so just skip the wait - // for the first draw and notify the divider to start animating now - splitScreen.onRecentsDrawn(); - }); - return true; - } - } else { - Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text, - Toast.LENGTH_SHORT).show(); - } - } - return false; - } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index f11683dedbda..0ae1170a25fc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -75,9 +75,6 @@ import com.android.systemui.navigationbar.NavigationBar; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.pip.Pip; -import com.android.systemui.pip.PipAnimationController; -import com.android.systemui.pip.phone.PipUtils; import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.shared.recents.IOverviewProxy; @@ -94,6 +91,9 @@ import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; 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.pip.phone.PipUtils; import com.android.wm.shell.splitscreen.SplitScreen; import java.io.FileDescriptor; @@ -108,6 +108,7 @@ import javax.inject.Inject; import dagger.Lazy; + /** * Class to send information from overview to launcher with a binder. */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index df61fd19ad45..6f6dd9cb22c7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -19,7 +19,6 @@ package com.android.systemui.recents; import android.content.ContentResolver; import android.content.Context; import android.content.res.Configuration; -import android.graphics.Rect; import android.provider.Settings; import com.android.systemui.SystemUI; @@ -120,17 +119,6 @@ public class Recents extends SystemUI implements CommandQueue.Callbacks { mImpl.cancelPreloadRecentApps(); } - public boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds, - int metricsDockAction) { - // Ensure the device has been provisioned before allowing the user to interact with - // recents - if (!isUserSetup()) { - return false; - } - - return mImpl.splitPrimaryTask(stackCreateMode, initialBounds, metricsDockAction); - } - /** * @return whether this device is provisioned and the current user is set up. */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java index a641730ac64e..8848dbbda5e7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java @@ -17,7 +17,6 @@ package com.android.systemui.recents; import android.content.Context; import android.content.res.Configuration; -import android.graphics.Rect; import java.io.PrintWriter; @@ -35,10 +34,6 @@ public interface RecentsImplementation { default void showRecentApps(boolean triggeredFromAltTab) {} default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {} default void toggleRecentApps() {} - default boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds, - int metricsDockAction) { - return false; - } default void dump(PrintWriter pw) {} } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index 26d408fe4ab7..c7a8fa22d1af 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -40,6 +40,11 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { val userHandle: UserHandle /** + * [UserInfo] for current user + */ + val userInfo: UserInfo + + /** * List of profiles associated with the current user. */ val userProfiles: List<UserInfo> diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 4cc0eeee712c..049685f3dda4 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -82,6 +82,12 @@ class UserTrackerImpl internal constructor( override val userContentResolver: ContentResolver get() = userContext.contentResolver + override val userInfo: UserInfo + get() { + val user = userId + return userProfiles.first { it.id == user } + } + /** * Returns a [List<UserInfo>] of all profiles associated with the current user. * diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java index b9b4f42a66e1..6202057b9ef1 100644 --- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java @@ -16,9 +16,6 @@ package com.android.systemui.shortcut; -import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; -import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; - import android.content.Context; import android.content.res.Configuration; import android.os.RemoteException; @@ -29,7 +26,6 @@ import android.view.WindowManagerGlobal; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.recents.Recents; import com.android.wm.shell.splitscreen.DividerView; import com.android.wm.shell.splitscreen.SplitScreen; @@ -46,7 +42,6 @@ public class ShortcutKeyDispatcher extends SystemUI private static final String TAG = "ShortcutKeyDispatcher"; private final Optional<SplitScreen> mSplitScreenOptional; - private final Recents mRecents; private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this); private IWindowManager mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); @@ -60,11 +55,9 @@ public class ShortcutKeyDispatcher extends SystemUI protected final long SC_DOCK_RIGHT = META_MASK | KeyEvent.KEYCODE_RIGHT_BRACKET; @Inject - public ShortcutKeyDispatcher(Context context, - Optional<SplitScreen> splitScreenOptional, Recents recents) { + public ShortcutKeyDispatcher(Context context, Optional<SplitScreen> splitScreenOptional) { super(context); mSplitScreenOptional = splitScreenOptional; - mRecents = recents; } /** @@ -96,8 +89,7 @@ public class ShortcutKeyDispatcher extends SystemUI } private void handleDockKey(long shortcutCode) { - if (mSplitScreenOptional.isPresent()) { - SplitScreen splitScreen = mSplitScreenOptional.get(); + mSplitScreenOptional.ifPresent(splitScreen -> { if (splitScreen.isDividerVisible()) { // If there is already a docked window, we respond by resizing the docking pane. DividerView dividerView = splitScreen.getDividerView(); @@ -112,12 +104,9 @@ public class ShortcutKeyDispatcher extends SystemUI dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */, true /* logMetrics */); return; + } else { + splitScreen.splitPrimaryTask(); } - } - - // Split the screen - mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT) - ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT - : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1); + }); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 37ae791bf172..0184fa7a5bfd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -58,12 +58,14 @@ import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.view.AppearanceRegion; import com.android.systemui.statusbar.CommandQueue.Callbacks; +import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.tracing.ProtoTracer; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; /** * This class takes the functions from IStatusBar that come in on @@ -159,6 +161,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< */ private int mLastUpdatedImeDisplayId = INVALID_DISPLAY; private ProtoTracer mProtoTracer; + private final @Nullable CommandRegistry mRegistry; /** * These methods are called back on the main thread. @@ -368,11 +371,12 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } public CommandQueue(Context context) { - this(context, null); + this(context, null, null); } - public CommandQueue(Context context, ProtoTracer protoTracer) { + public CommandQueue(Context context, ProtoTracer protoTracer, CommandRegistry registry) { mProtoTracer = protoTracer; + mRegistry = registry; context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler); // We always have default display. setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE); @@ -1013,6 +1017,34 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } } + @Override + public void passThroughShellCommand(String[] args, ParcelFileDescriptor pfd) { + final FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor()); + final PrintWriter pw = new PrintWriter(fos); + // This is mimicking Binder#dumpAsync, but on this side of the binder. Might be possible + // to just throw this work onto the handler just like the other messages + Thread thr = new Thread("Sysui.passThroughShellCommand") { + public void run() { + try { + if (mRegistry == null) { + return; + } + + // Registry blocks this thread until finished + mRegistry.onShellCommand(pw, args); + } finally { + pw.flush(); + try { + // Close the file descriptor so the TransferPipe finishes its thread + pfd.close(); + } catch (Exception e) { + } + } + } + }; + thr.start(); + } + private final class H extends Handler { private H(Looper l) { super(l); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 8bf134d9c5b9..bb76ac0b26bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -40,6 +40,9 @@ import android.os.Trace; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; +import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationStats; +import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.Log; import android.view.View; @@ -52,13 +55,18 @@ import com.android.systemui.Dumpable; import com.android.systemui.Interpolators; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.media.MediaData; import com.android.systemui.media.MediaDataManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.dagger.StatusBarModule; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; @@ -77,6 +85,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import dagger.Lazy; @@ -106,6 +115,9 @@ public class NotificationMediaManager implements Dumpable { private final NotificationEntryManager mEntryManager; private final MediaDataManager mMediaDataManager; + private final NotifPipeline mNotifPipeline; + private final NotifCollection mNotifCollection; + private final boolean mUsingNotifPipeline; @Nullable private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController; @@ -189,6 +201,9 @@ public class NotificationMediaManager implements Dumpable { NotificationEntryManager notificationEntryManager, MediaArtworkProcessor mediaArtworkProcessor, KeyguardBypassController keyguardBypassController, + NotifPipeline notifPipeline, + NotifCollection notifCollection, + FeatureFlags featureFlags, @Main DelayableExecutor mainExecutor, DeviceConfigProxy deviceConfig, MediaDataManager mediaDataManager) { @@ -206,17 +221,87 @@ public class NotificationMediaManager implements Dumpable { mEntryManager = notificationEntryManager; mMainExecutor = mainExecutor; mMediaDataManager = mediaDataManager; + mNotifPipeline = notifPipeline; + mNotifCollection = notifCollection; - notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { + if (!featureFlags.isNewNotifPipelineRenderingEnabled()) { + setupNEM(); + mUsingNotifPipeline = false; + } else { + setupNotifPipeline(); + mUsingNotifPipeline = true; + } + + mShowCompactMediaSeekbar = "true".equals( + DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED)); + + deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, + mContext.getMainExecutor(), + mPropertiesChangedListener); + } + + private void setupNotifPipeline() { + mNotifPipeline.addCollectionListener(new NotifCollectionListener() { + @Override + public void onEntryAdded(@NonNull NotificationEntry entry) { + mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); + } + + @Override + public void onEntryUpdated(NotificationEntry entry) { + mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); + } + + @Override + public void onEntryBind(NotificationEntry entry, StatusBarNotification sbn) { + findAndUpdateMediaNotifications(); + } + + @Override + public void onEntryRemoved(@NonNull NotificationEntry entry, int reason) { + removeEntry(entry); + } + + @Override + public void onEntryCleanUp(@NonNull NotificationEntry entry) { + removeEntry(entry); + } + }); + + mMediaDataManager.addListener(new MediaDataManager.Listener() { + @Override + public void onMediaDataLoaded(@NonNull String key, + @Nullable String oldKey, @NonNull MediaData data) { + } + + @Override + public void onMediaDataRemoved(@NonNull String key) { + mNotifPipeline.getAllNotifs() + .stream() + .filter(entry -> Objects.equals(entry.getKey(), key)) + .findAny() + .ifPresent(entry -> { + // TODO(b/160713608): "removing" this notification won't happen and + // won't send the 'deleteIntent' if the notification is ongoing. + mNotifCollection.dismissNotification(entry, + getDismissedByUserStats(entry)); + }); + } + }); + } + + private void setupNEM() { + mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override public void onPendingEntryAdded(NotificationEntry entry) { - mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); + mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); } @Override public void onPreEntryUpdated(NotificationEntry entry) { - mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); + mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); } @Override @@ -231,8 +316,8 @@ public class NotificationMediaManager implements Dumpable { @Override public void onEntryRemoved( - NotificationEntry entry, - NotificationVisibility visibility, + @NonNull NotificationEntry entry, + @Nullable NotificationVisibility visibility, boolean removedByUser, int reason) { removeEntry(entry); @@ -242,20 +327,49 @@ public class NotificationMediaManager implements Dumpable { // Pending entries are never inflated, and will never generate a call to onEntryRemoved(). // This can happen when notifications are added and canceled before inflation. Add this // separate listener for cleanup, since media inflation occurs onPendingEntryAdded(). - notificationEntryManager.addCollectionListener(new NotifCollectionListener() { + mEntryManager.addCollectionListener(new NotifCollectionListener() { @Override public void onEntryCleanUp(@NonNull NotificationEntry entry) { removeEntry(entry); } }); - mShowCompactMediaSeekbar = "true".equals( - DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED)); + mMediaDataManager.addListener(new MediaDataManager.Listener() { + @Override + public void onMediaDataLoaded(@NonNull String key, + @Nullable String oldKey, @NonNull MediaData data) { + } - deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mContext.getMainExecutor(), - mPropertiesChangedListener); + @Override + public void onMediaDataRemoved(@NonNull String key) { + NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key); + if (entry != null) { + // TODO(b/160713608): "removing" this notification won't happen and + // won't send the 'deleteIntent' if the notification is ongoing. + mEntryManager.performRemoveNotification(entry.getSbn(), + getDismissedByUserStats(entry), + NotificationListenerService.REASON_CANCEL); + } + } + }); + } + + private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) { + final int activeNotificationsCount; + if (mUsingNotifPipeline) { + activeNotificationsCount = mNotifPipeline.getShadeListCount(); + } else { + activeNotificationsCount = mEntryManager.getActiveNotificationsCount(); + } + return new DismissedByUserStats( + NotificationStats.DISMISSAL_SHADE, // Add DISMISSAL_MEDIA? + NotificationStats.DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain( + entry.getKey(), + entry.getRanking().getRank(), + activeNotificationsCount, + /* visible= */ true, + NotificationLogger.getNotificationLocation(entry))); } private void removeEntry(NotificationEntry entry) { @@ -299,14 +413,24 @@ public class NotificationMediaManager implements Dumpable { if (mMediaNotificationKey == null) { return null; } - synchronized (mEntryManager) { - NotificationEntry entry = mEntryManager + if (mUsingNotifPipeline) { + // TODO(b/169655596): Either add O(1) lookup, or cache this icon? + return mNotifPipeline.getAllNotifs().stream() + .filter(entry -> Objects.equals(entry.getKey(), mMediaNotificationKey)) + .findAny() + .map(entry -> entry.getIcons().getShelfIcon()) + .map(StatusBarIconView::getSourceIcon) + .orElse(null); + } else { + synchronized (mEntryManager) { + NotificationEntry entry = mEntryManager .getActiveNotificationUnfiltered(mMediaNotificationKey); - if (entry == null || entry.getIcons().getShelfIcon() == null) { - return null; - } + if (entry == null || entry.getIcons().getShelfIcon() == null) { + return null; + } - return entry.getIcons().getShelfIcon().getSourceIcon(); + return entry.getIcons().getShelfIcon().getSourceIcon(); + } } } @@ -321,94 +445,110 @@ public class NotificationMediaManager implements Dumpable { } public void findAndUpdateMediaNotifications() { + boolean metaDataChanged; + if (mUsingNotifPipeline) { + // TODO(b/169655907): get the semi-filtered notifications for current user + Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs(); + metaDataChanged = findPlayingMediaNotification(allNotifications); + } else { + synchronized (mEntryManager) { + Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs(); + metaDataChanged = findPlayingMediaNotification(allNotifications); + } + + if (metaDataChanged) { + mEntryManager.updateNotifications("NotificationMediaManager - metaDataChanged"); + } + + } + dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */); + } + + /** + * Find a notification and media controller associated with the playing media session, and + * update this manager's internal state. + * @return whether the current MediaMetadata changed (and needs to be announced to listeners). + */ + private boolean findPlayingMediaNotification( + @NonNull Collection<NotificationEntry> allNotifications) { boolean metaDataChanged = false; + // Promote the media notification with a controller in 'playing' state, if any. + NotificationEntry mediaNotification = null; + MediaController controller = null; + for (NotificationEntry entry : allNotifications) { + if (entry.isMediaNotification()) { + final MediaSession.Token token = + entry.getSbn().getNotification().extras.getParcelable( + Notification.EXTRA_MEDIA_SESSION); + if (token != null) { + MediaController aController = new MediaController(mContext, token); + if (PlaybackState.STATE_PLAYING + == getMediaControllerPlaybackState(aController)) { + if (DEBUG_MEDIA) { + Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching " + + entry.getSbn().getKey()); + } + mediaNotification = entry; + controller = aController; + break; + } + } + } + } + if (mediaNotification == null) { + // Still nothing? OK, let's just look for live media sessions and see if they match + // one of our notifications. This will catch apps that aren't (yet!) using media + // notifications. + + if (mMediaSessionManager != null) { + // TODO: Should this really be for all users? It appears that inactive users + // can't have active sessions, which would mean it is fine. + final List<MediaController> sessions = + mMediaSessionManager.getActiveSessionsForUser(null, UserHandle.USER_ALL); - synchronized (mEntryManager) { - Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs(); - - // Promote the media notification with a controller in 'playing' state, if any. - NotificationEntry mediaNotification = null; - MediaController controller = null; - for (NotificationEntry entry : allNotifications) { - if (entry.isMediaNotification()) { - final MediaSession.Token token = - entry.getSbn().getNotification().extras.getParcelable( - Notification.EXTRA_MEDIA_SESSION); - if (token != null) { - MediaController aController = new MediaController(mContext, token); - if (PlaybackState.STATE_PLAYING == - getMediaControllerPlaybackState(aController)) { + for (MediaController aController : sessions) { + // now to see if we have one like this + final String pkg = aController.getPackageName(); + + for (NotificationEntry entry : allNotifications) { + if (entry.getSbn().getPackageName().equals(pkg)) { if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching " + Log.v(TAG, "DEBUG_MEDIA: found controller matching " + entry.getSbn().getKey()); } - mediaNotification = entry; controller = aController; + mediaNotification = entry; break; } } } } - if (mediaNotification == null) { - // Still nothing? OK, let's just look for live media sessions and see if they match - // one of our notifications. This will catch apps that aren't (yet!) using media - // notifications. - - if (mMediaSessionManager != null) { - // TODO: Should this really be for all users? - final List<MediaController> sessions - = mMediaSessionManager.getActiveSessionsForUser( - null, - UserHandle.USER_ALL); - - for (MediaController aController : sessions) { - // now to see if we have one like this - final String pkg = aController.getPackageName(); - - for (NotificationEntry entry : allNotifications) { - if (entry.getSbn().getPackageName().equals(pkg)) { - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: found controller matching " - + entry.getSbn().getKey()); - } - controller = aController; - mediaNotification = entry; - break; - } - } - } - } - } - - if (controller != null && !sameSessions(mMediaController, controller)) { - // We have a new media session - clearCurrentMediaNotificationSession(); - mMediaController = controller; - mMediaController.registerCallback(mMediaListener); - mMediaMetadata = mMediaController.getMetadata(); - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: " - + mMediaController + ", receive metadata: " + mMediaMetadata); - } + } - metaDataChanged = true; + if (controller != null && !sameSessions(mMediaController, controller)) { + // We have a new media session + clearCurrentMediaNotificationSession(); + mMediaController = controller; + mMediaController.registerCallback(mMediaListener); + mMediaMetadata = mMediaController.getMetadata(); + if (DEBUG_MEDIA) { + Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: " + + mMediaController + ", receive metadata: " + mMediaMetadata); } - if (mediaNotification != null - && !mediaNotification.getSbn().getKey().equals(mMediaNotificationKey)) { - mMediaNotificationKey = mediaNotification.getSbn().getKey(); - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key=" - + mMediaNotificationKey); - } - } + metaDataChanged = true; } - if (metaDataChanged) { - mEntryManager.updateNotifications("NotificationMediaManager - metaDataChanged"); + if (mediaNotification != null + && !mediaNotification.getSbn().getKey().equals(mMediaNotificationKey)) { + mMediaNotificationKey = mediaNotification.getSbn().getKey(); + if (DEBUG_MEDIA) { + Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key=" + + mMediaNotificationKey); + } } - dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */); + return metaDataChanged; } public void clearCurrentMediaNotification() { @@ -428,7 +568,7 @@ public class NotificationMediaManager implements Dumpable { } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.print(" mMediaSessionManager="); pw.println(mMediaSessionManager); pw.print(" mMediaNotificationKey="); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt new file mode 100644 index 000000000000..ce0a08cd4ccf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt @@ -0,0 +1,204 @@ +/* + * 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.commandline + +import android.content.Context + +import com.android.systemui.Prefs +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main + +import java.io.PrintWriter +import java.lang.IllegalStateException +import java.util.concurrent.Executor +import java.util.concurrent.FutureTask + +import javax.inject.Inject + +/** + * Registry / dispatcher for incoming shell commands. See [StatusBarManagerService] and + * [StatusBarShellCommand] for how things are set up. Commands come in here by way of the service + * like so: + * + * `adb shell cmd statusbar <command>` + * + * Where `cmd statusbar` send the shell command through to StatusBarManagerService, and + * <command> is either processed in system server, or sent through to IStatusBar (CommandQueue) + */ +@SysUISingleton +class CommandRegistry @Inject constructor( + val context: Context, + @Main val mainExecutor: Executor +) { + // To keep the command line parser hermetic, create a new one for every shell command + private val commandMap = mutableMapOf<String, CommandWrapper>() + private var initialized = false + + /** + * Register a [Command] for a given name. The name here is the top-level namespace for + * the registered command. A command could look like this for instance: + * + * `adb shell cmd statusbar notifications list` + * + * Where `notifications` is the command that signifies which receiver to send the remaining args + * to. + * + * @param command String name of the command to register. Currently does not support aliases + * @param receiverFactory Creates an instance of the receiver on every command + * @param executor Pass an executor to offload your `receive` to another thread + */ + @Synchronized + fun registerCommand( + name: String, + commandFactory: () -> Command, + executor: Executor + ) { + if (commandMap[name] != null) { + throw IllegalStateException("A command is already registered for ($name)") + } + commandMap[name] = CommandWrapper(commandFactory, executor) + } + + /** + * Register a [Command] for a given name, to be executed on the main thread. + */ + @Synchronized + fun registerCommand(name: String, commandFactory: () -> Command) { + registerCommand(name, commandFactory, mainExecutor) + } + + /** Unregister a receiver */ + @Synchronized + fun unregisterCommand(command: String) { + commandMap.remove(command) + } + + private fun initializeCommands() { + initialized = true + // TODO: Might want a dedicated place for commands without a home. Currently + // this is here because Prefs.java is just an interface + registerCommand("prefs") { PrefsCommand(context) } + } + + /** + * Receive a shell command and dispatch to the appropriate [Command]. Blocks until finished. + */ + fun onShellCommand(pw: PrintWriter, args: Array<String>) { + if (!initialized) initializeCommands() + + if (args.isEmpty()) { + help(pw) + return + } + + val commandName = args[0] + val wrapper = commandMap[commandName] + + if (wrapper == null) { + help(pw) + return + } + + // Create a new instance of the command + val command = wrapper.commandFactory() + + // Wrap the receive command in a task so that we can wait for its completion + val task = FutureTask<Unit> { + command.execute(pw, args.drop(1)) + } + + wrapper.executor.execute { + task.run() + } + + // Wait for the future to complete + task.get() + } + + private fun help(pw: PrintWriter) { + pw.println("Usage: adb shell cmd statusbar <command>") + pw.println(" known commands:") + for (k in commandMap.keys) { + pw.println(" $k") + } + } +} + +private const val TAG = "CommandRegistry" + +interface Command { + fun execute(pw: PrintWriter, args: List<String>) + fun help(pw: PrintWriter) +} + +// Wrap commands in an executor package +private data class CommandWrapper(val commandFactory: () -> Command, val executor: Executor) + +// Commands can go here for now, but they should move outside + +private class PrefsCommand(val context: Context) : Command { + override fun help(pw: PrintWriter) { + pw.println("usage: prefs <command> [args]") + pw.println("Available commands:") + pw.println(" list-prefs") + pw.println(" set-pref <pref name> <value>") + } + + override fun execute(pw: PrintWriter, args: List<String>) { + if (args.isEmpty()) { + help(pw) + return + } + + val topLevel = args[0] + + when (topLevel) { + "list-prefs" -> listPrefs(pw) + "set-pref" -> setPref(pw, args.drop(1)) + else -> help(pw) + } + } + + private fun listPrefs(pw: PrintWriter) { + pw.println("Available keys:") + for (field in Prefs.Key::class.java.declaredFields) { + pw.print(" ") + pw.println(field.get(Prefs.Key::class.java)) + } + } + + /** + * Sets a preference from [Prefs] + */ + private fun setPref(pw: PrintWriter, args: List<String>) { + if (args.isEmpty()) { + pw.println("invalid arguments: $args") + return + } + val pref = args[0] + + when (pref) { + Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING -> { + val value = Integer.parseInt(args[1]) + Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, value != 0) + } + else -> { + pw.println("Cannot set pref ($pref)") + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index 969cd90e61d9..cee9c70f53eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -28,6 +28,7 @@ import com.android.systemui.media.MediaDataManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.MediaArtworkProcessor; import com.android.systemui.statusbar.NotificationClickNotifier; import com.android.systemui.statusbar.NotificationListener; @@ -39,10 +40,13 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.notification.AssistantFeedbackController; import com.android.systemui.statusbar.notification.DynamicChildBindController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; @@ -110,6 +114,9 @@ public interface StatusBarDependenciesModule { NotificationEntryManager notificationEntryManager, MediaArtworkProcessor mediaArtworkProcessor, KeyguardBypassController keyguardBypassController, + NotifPipeline notifPipeline, + NotifCollection notifCollection, + FeatureFlags featureFlags, @Main DelayableExecutor mainExecutor, DeviceConfigProxy deviceConfigProxy, MediaDataManager mediaDataManager) { @@ -120,6 +127,9 @@ public interface StatusBarDependenciesModule { notificationEntryManager, mediaArtworkProcessor, keyguardBypassController, + notifPipeline, + notifCollection, + featureFlags, mainExecutor, deviceConfigProxy, mediaDataManager); @@ -192,8 +202,11 @@ public interface StatusBarDependenciesModule { */ @Provides @SysUISingleton - static CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) { - return new CommandQueue(context, protoTracer); + static CommandQueue provideCommandQueue( + Context context, + ProtoTracer protoTracer, + CommandRegistry registry) { + return new CommandQueue(context, protoTracer, registry); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index fdfd72489e93..d617dff372da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -291,6 +291,7 @@ public class NotifCollection implements Dumpable { mLogger.logDismissAll(userId); try { + // TODO(b/169585328): Do not clear media player notifications mStatusBarService.onClearAllNotifications(userId); } catch (RemoteException e) { // system process is dead if we're here. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt index 22ca4961320c..7babbb40b6c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt @@ -172,14 +172,19 @@ class ShadeViewDiffer( private fun treeToMap(tree: NodeSpec): Map<NodeController, NodeSpec> { val map = mutableMapOf<NodeController, NodeSpec>() - registerNodes(tree, map) + try { + registerNodes(tree, map) + } catch (ex: DuplicateNodeException) { + logger.logDuplicateNodeInTree(tree, ex) + throw ex + } return map } private fun registerNodes(node: NodeSpec, map: MutableMap<NodeController, NodeSpec>) { if (map.containsKey(node.controller)) { - throw RuntimeException("Node ${node.controller.nodeLabel} appears more than once") + throw DuplicateNodeException("Node ${node.controller.nodeLabel} appears more than once") } map[node.controller] = node @@ -191,6 +196,8 @@ class ShadeViewDiffer( } } +private class DuplicateNodeException(message: String) : RuntimeException(message) + private class ShadeNode( val controller: NodeController ) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt index 19e156f572d4..d27455004c01 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotificationLog +import java.lang.RuntimeException import javax.inject.Inject class ShadeViewDifferLogger @Inject constructor( @@ -67,6 +68,15 @@ class ShadeViewDifferLogger @Inject constructor( "Moving child view $str1 in $str2 to index $int1" }) } + + fun logDuplicateNodeInTree(node: NodeSpec, ex: RuntimeException) { + buffer.log(TAG, LogLevel.ERROR, { + str1 = ex.toString() + str2 = treeSpecToStr(node) + }, { + "$str1 when mapping tree: $str2" + }) + } } private const val TAG = "NotifViewManager"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 500de2d29d03..93204995c5b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING; @@ -73,6 +74,7 @@ import android.widget.ScrollView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.keyguard.KeyguardSliceView; import com.android.settingslib.Utils; @@ -97,7 +99,6 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.ShadeViewRefactor; import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.logging.NotificationLogger; @@ -260,6 +261,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private boolean mDismissAllInProgress; private boolean mFadeNotificationsOnDismiss; private FooterDismissListener mFooterDismissListener; + private boolean mFlingAfterUpEvent; /** * Was the scroller scrolled to the top when the down motion was observed? @@ -3789,6 +3791,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if ((Math.abs(initialVelocity) > mMinimumVelocity)) { float currentOverScrollTop = getCurrentOverScrollAmount(true); if (currentOverScrollTop == 0.0f || initialVelocity > 0) { + mFlingAfterUpEvent = true; + setFinishScrollingCallback(() -> { + mFlingAfterUpEvent = false; + InteractionJankMonitor.getInstance() + .end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); + setFinishScrollingCallback(null); + }); fling(-initialVelocity); } else { onOverScrollFling(false, initialVelocity); @@ -3840,6 +3849,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return true; } + boolean isFlingAfterUpEvent() { + return mFlingAfterUpEvent; + } + @ShadeViewRefactor(RefactorComponent.INPUT) protected boolean isInsideQsContainer(MotionEvent ev) { return ev.getY() < mQsContainer.getBottom(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 703c214ed3ac..7698133e1521 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.stack; import static android.service.notification.NotificationStats.DISMISSAL_SHADE; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener; @@ -47,6 +48,7 @@ import android.view.WindowInsets; import android.widget.FrameLayout; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; @@ -1301,7 +1303,8 @@ public class NotificationStackScrollLayoutController { mView.clearNotifications(ROWS_GENTLE, closeShade); } - private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove, int selectedRows) { + private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove, + @SelectedRows int selectedRows) { if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { if (selectedRows == ROWS_ALL) { mNotifCollection.dismissAllNotifications( @@ -1334,6 +1337,7 @@ public class NotificationStackScrollLayoutController { } if (selectedRows == ROWS_ALL) { try { + // TODO(b/169585328): Do not clear media player notifications mIStatusBarService.onClearAllNotifications( mLockscreenUserManager.getCurrentUserId()); } catch (Exception ignored) { @@ -1556,6 +1560,14 @@ public class NotificationStackScrollLayoutController { if (ev.getActionMasked() == MotionEvent.ACTION_UP) { mView.setCheckForLeaveBehind(true); } + + // When swiping directly on the NSSL, this would only get an onTouchEvent. + // We log any touches other than down, which will be captured by onTouchEvent. + // In the intercept we only start tracing when it's not a down (otherwise that down + // would be duplicated when intercepted). + if (scrollWantsIt && ev.getActionMasked() != MotionEvent.ACTION_DOWN) { + InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); + } return swipeWantsIt || scrollWantsIt || expandWantsIt; } @@ -1611,7 +1623,32 @@ public class NotificationStackScrollLayoutController { if (ev.getActionMasked() == MotionEvent.ACTION_UP) { mView.setCheckForLeaveBehind(true); } + traceJankOnTouchEvent(ev.getActionMasked(), scrollerWantsIt); return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt; } + + private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) { + // Handle interaction jank monitor cases. + switch (action) { + case MotionEvent.ACTION_DOWN: + if (scrollerWantsIt) { + InteractionJankMonitor.getInstance() + .begin(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); + } + break; + case MotionEvent.ACTION_UP: + if (scrollerWantsIt && !mView.isFlingAfterUpEvent()) { + InteractionJankMonitor.getInstance() + .end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); + } + break; + case MotionEvent.ACTION_CANCEL: + if (scrollerWantsIt) { + InteractionJankMonitor.getInstance() + .cancel(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); + } + break; + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 95edfe37fee7..d7a8202d7a4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -29,6 +29,7 @@ import com.android.systemui.R; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.FooterView; @@ -687,15 +688,27 @@ public class StackScrollAlgorithm { AmbientState ambientState) { int childCount = algorithmState.visibleChildren.size(); float childrenOnTop = 0.0f; + + int topHunIndex = -1; + for (int i = 0; i < childCount; i++) { + ExpandableView child = algorithmState.visibleChildren.get(i); + if (child instanceof ActivatableNotificationView + && (child.isAboveShelf() || child.showingPulsing())) { + topHunIndex = i; + break; + } + } + for (int i = childCount - 1; i >= 0; i--) { childrenOnTop = updateChildZValue(i, childrenOnTop, - algorithmState, ambientState); + algorithmState, ambientState, i == topHunIndex); } } protected float updateChildZValue(int i, float childrenOnTop, StackScrollAlgorithmState algorithmState, - AmbientState ambientState) { + AmbientState ambientState, + boolean shouldElevateHun) { ExpandableView child = algorithmState.visibleChildren.get(i); ExpandableViewState childViewState = child.getViewState(); int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements(); @@ -713,8 +726,7 @@ public class StackScrollAlgorithm { } childViewState.zTranslation = baseZ + childrenOnTop * zDistanceBetweenElements; - } else if (child == ambientState.getTrackedHeadsUpRow() - || (i == 0 && (child.isAboveShelf() || child.showingPulsing()))) { + } else if (shouldElevateHun) { // In case this is a new view that has never been measured before, we don't want to // elevate if we are currently expanded more then the notification int shelfHeight = ambientState.getShelf() == null ? 0 : diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index efd6767e66a7..76c5baf6e9f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -169,7 +169,8 @@ public class AutoTileManager implements UserAwareController { String setting = split[0]; String spec = split[1]; // Populate all the settings. As they may not have been added in other users - AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec); + AutoAddSetting s = new AutoAddSetting( + mContext, mHandler, setting, mCurrentUser.getIdentifier(), spec); mAutoAddSettingList.add(s); } else { Log.w(TAG, "Malformed item in array: " + tile); @@ -319,8 +320,14 @@ public class AutoTileManager implements UserAwareController { private class AutoAddSetting extends SecureSetting { private final String mSpec; - AutoAddSetting(Context context, Handler handler, String setting, String tileSpec) { - super(context, handler, setting); + AutoAddSetting( + Context context, + Handler handler, + String setting, + int userId, + String tileSpec + ) { + super(context, handler, setting, userId); mSpec = tileSpec; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index 3d51854a348c..54fb863b5de7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -168,7 +168,7 @@ public class LockIcon extends KeyguardAffordanceView { int iconRes = isAnim ? getThemedAnimationResId(lockAnimIndex) : getIconForState(newState); if (!mDrawableCache.contains(iconRes)) { - mDrawableCache.put(iconRes, getResources().getDrawable(iconRes)); + mDrawableCache.put(iconRes, getContext().getDrawable(iconRes)); } return mDrawableCache.get(iconRes); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index cd9cc0775c66..3f636ffe3a2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone; import static android.view.View.GONE; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; @@ -62,6 +64,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.LatencyTracker; @@ -1139,6 +1142,7 @@ public class NotificationPanelViewController extends PanelViewController { onQsExpansionStarted(); mInitialHeightOnTouch = mQsExpansionHeight; mQsTracking = true; + traceQsJank(true /* startTracing */, false /* wasCancelled */); mNotificationStackScrollLayoutController.cancelLongPress(); } break; @@ -1170,6 +1174,7 @@ public class NotificationPanelViewController extends PanelViewController { && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { mView.getParent().requestDisallowInterceptTouchEvent(true); mQsTracking = true; + traceQsJank(true /* startTracing */, false /* wasCancelled */); onQsExpansionStarted(); notifyExpandingFinished(); mInitialHeightOnTouch = mQsExpansionHeight; @@ -1202,6 +1207,19 @@ public class NotificationPanelViewController extends PanelViewController { && x < stackScrollerX + mNotificationStackScrollLayoutController.getWidth(); } + private void traceQsJank(boolean startTracing, boolean wasCancelled) { + InteractionJankMonitor monitor = InteractionJankMonitor.getInstance(); + if (startTracing) { + monitor.begin(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); + } else { + if (wasCancelled) { + monitor.cancel(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); + } else { + monitor.end(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); + } + } + } + private void initDownStates(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { mOnlyAffordanceInThisMotion = false; @@ -1315,9 +1333,9 @@ public class NotificationPanelViewController extends PanelViewController { final int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f && mBarState != KEYGUARD && !mQsExpanded && mQsExpansionEnabled) { - // Down in the empty area while fully expanded - go to QS. mQsTracking = true; + traceQsJank(true /* startTracing */, false /* wasCancelled */); mConflictingQsExpansionGesture = true; onQsExpansionStarted(); mInitialHeightOnTouch = mQsExpansionHeight; @@ -1405,6 +1423,7 @@ public class NotificationPanelViewController extends PanelViewController { return; } mExpectingSynthesizedDown = true; + InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); onTrackingStarted(); updatePanelExpanded(); } @@ -1474,6 +1493,7 @@ public class NotificationPanelViewController extends PanelViewController { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mQsTracking = true; + traceQsJank(true /* startTracing */, false /* wasCancelled */); mInitialTouchY = y; mInitialTouchX = x; onQsExpansionStarted(); @@ -1513,6 +1533,9 @@ public class NotificationPanelViewController extends PanelViewController { if (fraction != 0f || y >= mInitialTouchY) { flingQsWithCurrentVelocity(y, event.getActionMasked() == MotionEvent.ACTION_CANCEL); + } else { + traceQsJank(false /* startTracing */, + event.getActionMasked() == MotionEvent.ACTION_CANCEL); } if (mQsVelocityTracker != null) { mQsVelocityTracker.recycle(); @@ -1893,7 +1916,7 @@ public class NotificationPanelViewController extends PanelViewController { * @see #flingSettings(float, int, Runnable, boolean) */ public void flingSettings(float vel, int type) { - flingSettings(vel, type, null, false /* isClick */); + flingSettings(vel, type, null /* onFinishRunnable */, false /* isClick */); } /** @@ -1923,6 +1946,7 @@ public class NotificationPanelViewController extends PanelViewController { if (onFinishRunnable != null) { onFinishRunnable.run(); } + traceQsJank(false /* startTracing */, type != FLING_EXPAND /* wasCancelled */); return; } @@ -1947,12 +1971,18 @@ public class NotificationPanelViewController extends PanelViewController { setQsExpansion((Float) animation.getAnimatedValue()); }); animator.addListener(new AnimatorListenerAdapter() { + private boolean mIsCanceled; @Override public void onAnimationStart(Animator animation) { notifyExpandingStarted(); } @Override + public void onAnimationCancel(Animator animation) { + mIsCanceled = true; + } + + @Override public void onAnimationEnd(Animator animation) { mAnimatingQS = false; notifyExpandingFinished(); @@ -1961,6 +1991,7 @@ public class NotificationPanelViewController extends PanelViewController { if (onFinishRunnable != null) { onFinishRunnable.run(); } + traceQsJank(false /* startTracing */, mIsCanceled /* wasCancelled */); } }); // Let's note that we're animating QS. Moving the animator here will cancel it immediately, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java index bc80a1a5137d..a4fc3a39c706 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java @@ -49,6 +49,7 @@ import android.view.WindowInsets; import android.view.WindowInsetsController; import android.widget.FrameLayout; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.view.FloatingActionMode; import com.android.internal.widget.FloatingToolbar; import com.android.systemui.R; @@ -145,6 +146,7 @@ public class NotificationShadeWindowView extends FrameLayout { protected void onAttachedToWindow() { super.onAttachedToWindow(); setWillNotDraw(!DEBUG); + InteractionJankMonitor.getInstance().init(this); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 6fa99ba41006..5a01f471d0cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; import static com.android.systemui.classifier.Classifier.UNLOCK; @@ -40,6 +41,7 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.animation.Interpolator; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.LatencyTracker; import com.android.systemui.DejankUtils; @@ -109,6 +111,7 @@ public abstract class PanelViewController { private boolean mMotionAborted; private boolean mUpwardsWhenThresholdReached; private boolean mAnimatingOnDown; + private boolean mHandlingPointerUp; private ValueAnimator mHeightAnimator; private ObjectAnimator mPeekAnimator; @@ -356,6 +359,9 @@ public abstract class PanelViewController { protected void startExpandMotion(float newX, float newY, boolean startTracking, float expandedHeight) { + if (!mHandlingPointerUp) { + InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + } mInitialOffsetOnTouch = expandedHeight; mInitialTouchY = newY; mInitialTouchX = newX; @@ -571,6 +577,7 @@ public abstract class PanelViewController { target = getMaxPanelHeight() - getClearAllHeightWithPadding(); } if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) { + InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); notifyExpandingFinished(); return; } @@ -622,7 +629,12 @@ public abstract class PanelViewController { } setAnimator(null); if (!mCancelled) { + InteractionJankMonitor.getInstance() + .end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); notifyExpandingFinished(); + } else { + InteractionJankMonitor.getInstance() + .cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); } notifyBarPanelExpansionChanged(); } @@ -1272,7 +1284,9 @@ public abstract class PanelViewController { final float newY = event.getY(newIndex); final float newX = event.getX(newIndex); mTrackingPointer = event.getPointerId(newIndex); + mHandlingPointerUp = true; startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight); + mHandlingPointerUp = false; } break; case MotionEvent.ACTION_POINTER_DOWN: @@ -1330,6 +1344,12 @@ public abstract class PanelViewController { case MotionEvent.ACTION_CANCEL: addMovement(event); endMotionEvent(event, x, y, false /* forceCancel */); + InteractionJankMonitor monitor = InteractionJankMonitor.getInstance(); + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + } else { + monitor.cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + } break; } return !mGestureWaitForTouchSlop || mTracking; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 994af090d21f..e7c29b6b54b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone; -import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; -import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; @@ -37,7 +35,6 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASL import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; import static com.android.systemui.shared.system.WindowManagerWrapper.NAV_BAR_POS_INVALID; -import static com.android.systemui.shared.system.WindowManagerWrapper.NAV_BAR_POS_LEFT; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; @@ -174,7 +171,6 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.Snoo import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSPanel; -import com.android.systemui.recents.Recents; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.WindowManagerWrapper; @@ -718,7 +714,6 @@ public class StatusBar extends SystemUI implements DemoMode, DozeScrimController dozeScrimController, VolumeComponent volumeComponent, CommandQueue commandQueue, - Optional<Recents> recentsOptional, Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, Optional<SplitScreen> splitScreenOptional, @@ -799,7 +794,6 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy; mVolumeComponent = volumeComponent; mCommandQueue = commandQueue; - mRecentsOptional = recentsOptional; mStatusBarComponentBuilder = statusBarComponentBuilder; mPluginManager = pluginManager; mSplitScreenOptional = splitScreenOptional; @@ -1548,35 +1542,37 @@ public class StatusBar extends SystemUI implements DemoMode, } public boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) { - if (!mRecentsOptional.isPresent()) { + if (!mSplitScreenOptional.isPresent()) { return false; } - if (mSplitScreenOptional.isPresent()) { - SplitScreen splitScreen = mSplitScreenOptional.get(); - if (splitScreen.isDividerVisible()) { - if (splitScreen.isMinimized() - && !splitScreen.isHomeStackResizable()) { - // Undocking from the minimized state is not supported - return false; - } else { - splitScreen.onUndockingTask(); - if (metricsUndockAction != -1) { - mMetricsLogger.action(metricsUndockAction); - } - } - return true; + final SplitScreen splitScreen = mSplitScreenOptional.get(); + if (splitScreen.isDividerVisible()) { + if (splitScreen.isMinimized() && !splitScreen.isHomeStackResizable()) { + // Undocking from the minimized state is not supported + return false; } + + splitScreen.onUndockingTask(); + if (metricsUndockAction != -1) { + mMetricsLogger.action(metricsUndockAction); + } + return true; } final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(mDisplayId); if (navbarPos == NAV_BAR_POS_INVALID) { return false; } - int createMode = navbarPos == NAV_BAR_POS_LEFT - ? SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT - : SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; - return mRecentsOptional.get().splitPrimaryTask(createMode, null, metricsDockAction); + + if (splitScreen.splitPrimaryTask()) { + if (metricsDockAction != -1) { + mMetricsLogger.action(metricsDockAction); + } + return true; + } + + return false; } /** @@ -4121,8 +4117,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected Display mDisplay; private int mDisplayId; - private final Optional<Recents> mRecentsOptional; - protected NotificationShelfController mNotificationShelfController; private final Lazy<AssistManager> mAssistManagerLazy; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 3f29a4ea5b00..6d4099b656cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -43,7 +43,6 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.PluginDependencyProvider; -import com.android.systemui.recents.Recents; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; @@ -175,7 +174,6 @@ public interface StatusBarPhoneModule { DozeScrimController dozeScrimController, VolumeComponent volumeComponent, CommandQueue commandQueue, - Optional<Recents> recentsOptional, Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, Optional<SplitScreen> splitScreenOptional, @@ -254,7 +252,6 @@ public interface StatusBarPhoneModule { dozeScrimController, volumeComponent, commandQueue, - recentsOptional, statusBarComponentBuilder, pluginManager, splitScreenOptional, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index 0fdc80b3d97a..8e8a33fd0d9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -20,7 +20,6 @@ import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; import static com.android.settingslib.Utils.updateLocationEnabled; -import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -43,6 +42,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; 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.systemui.util.Utils; import java.util.ArrayList; @@ -60,6 +60,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio private final Context mContext; private final AppOpsController mAppOpsController; private final BootCompleteCache mBootCompleteCache; + private final UserTracker mUserTracker; private final H mHandler; @@ -68,11 +69,13 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio @Inject public LocationControllerImpl(Context context, AppOpsController appOpsController, @Main Looper mainLooper, @Background Handler backgroundHandler, - BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache) { + BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache, + UserTracker userTracker) { mContext = context; mAppOpsController = appOpsController; mBootCompleteCache = bootCompleteCache; mHandler = new H(mainLooper); + mUserTracker = userTracker; // Register to listen for changes in location settings. IntentFilter filter = new IntentFilter(); @@ -113,7 +116,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio public boolean setLocationEnabled(boolean enabled) { // QuickSettings always runs as the owner, so specifically set the settings // for the current foreground user. - int currentUserId = ActivityManager.getCurrentUser(); + int currentUserId = mUserTracker.getUserId(); if (isUserLocationRestricted(currentUserId)) { return false; } @@ -134,7 +137,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio LocationManager locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); return mBootCompleteCache.isBootComplete() && locationManager.isLocationEnabledForUser( - UserHandle.of(ActivityManager.getCurrentUser())); + mUserTracker.getUserHandle()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java index 7a78c157e5b4..0bd36240a366 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java @@ -58,6 +58,10 @@ public class TvNotificationPanel extends SystemUI implements CommandQueue.Callba if (!mNotificationHandlerPackage.isEmpty()) { startNotificationHandlerActivity( new Intent(NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL)); + } else { + Log.w(TAG, + "Not toggling notification panel: config_notificationHandlerPackage is " + + "empty"); } } @@ -66,6 +70,10 @@ public class TvNotificationPanel extends SystemUI implements CommandQueue.Callba if (!mNotificationHandlerPackage.isEmpty()) { startNotificationHandlerActivity( new Intent(NotificationManager.ACTION_OPEN_NOTIFICATION_HANDLER_PANEL)); + } else { + Log.w(TAG, + "Not expanding notification panel: config_notificationHandlerPackage is " + + "empty"); } } @@ -77,6 +85,9 @@ public class TvNotificationPanel extends SystemUI implements CommandQueue.Callba NotificationManager.ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL); closeNotificationIntent.setPackage(mNotificationHandlerPackage); mContext.sendBroadcastAsUser(closeNotificationIntent, UserHandle.CURRENT); + } else { + Log.w(TAG, + "Not closing notification panel: config_notificationHandlerPackage is empty"); } } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java index 0070dcf9a604..4c724aeea9ae 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java @@ -15,6 +15,7 @@ */ package com.android.systemui.tuner; +import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -22,6 +23,7 @@ import android.content.DialogInterface; import android.hardware.display.AmbientDisplayConfiguration; import android.os.Build; import android.os.Bundle; +import android.os.UserHandle; import android.provider.Settings; import android.view.Menu; import android.view.MenuInflater; @@ -122,7 +124,8 @@ public class TunerFragment extends PreferenceFragment { getActivity().finish(); return true; case MENU_REMOVE: - TunerService.showResetRequest(getContext(), new Runnable() { + UserHandle user = new UserHandle(ActivityManager.getCurrentUser()); + TunerService.showResetRequest(getContext(), user, new Runnable() { @Override public void run() { if (getActivity() != null) { diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java index 338e1781abd0..70bba263ab90 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java @@ -14,7 +14,6 @@ package com.android.systemui.tuner; -import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -51,25 +50,24 @@ public abstract class TunerService { void onTuningChanged(String key, String newValue); } - private static Context userContext(Context context) { + private static Context userContext(Context context, UserHandle user) { try { - return context.createPackageContextAsUser(context.getPackageName(), 0, - new UserHandle(ActivityManager.getCurrentUser())); + return context.createPackageContextAsUser(context.getPackageName(), 0, user); } catch (NameNotFoundException e) { return context; } } - public static final void setTunerEnabled(Context context, boolean enabled) { - userContext(context).getPackageManager().setComponentEnabledSetting( + public static final void setTunerEnabled(Context context, UserHandle user, boolean enabled) { + userContext(context, user).getPackageManager().setComponentEnabledSetting( new ComponentName(context, TunerActivity.class), enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } - public static final boolean isTunerEnabled(Context context) { - return userContext(context).getPackageManager().getComponentEnabledSetting( + public static final boolean isTunerEnabled(Context context, UserHandle user) { + return userContext(context, user).getPackageManager().getComponentEnabledSetting( new ComponentName(context, TunerActivity.class)) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; } @@ -83,7 +81,8 @@ public abstract class TunerService { } } - public static final void showResetRequest(final Context context, final Runnable onDisabled) { + public static final void showResetRequest(final Context context, UserHandle user, + final Runnable onDisabled) { SystemUIDialog dialog = new SystemUIDialog(context); dialog.setShowForAllUsers(true); dialog.setMessage(R.string.remove_from_settings_prompt); @@ -91,20 +90,20 @@ public abstract class TunerService { (OnClickListener) null); dialog.setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Tell the tuner (in main SysUI process) to clear all its settings. - context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR)); - // Disable access to tuner. - TunerService.setTunerEnabled(context, false); - // Make them sit through the warning dialog again. - Settings.Secure.putInt(context.getContentResolver(), - TunerFragment.SETTING_SEEN_TUNER_WARNING, 0); - if (onDisabled != null) { - onDisabled.run(); - } - } - }); + @Override + public void onClick(DialogInterface dialog, int which) { + // Tell the tuner (in main SysUI process) to clear all its settings. + context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR)); + // Disable access to tuner. + TunerService.setTunerEnabled(context, user, false); + // Make them sit through the warning dialog again. + Settings.Secure.putInt(context.getContentResolver(), + TunerFragment.SETTING_SEEN_TUNER_WARNING, 0); + if (onDisabled != null) { + onDisabled.run(); + } + } + }); dialog.show(); } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java index d9727a73f651..22f03e074b06 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -15,13 +15,13 @@ */ package com.android.systemui.tuner; -import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Looper; import android.os.UserManager; import android.provider.Settings; @@ -37,7 +37,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.qs.QSTileHost; -import com.android.systemui.settings.CurrentUserTracker; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.util.leak.LeakDetector; @@ -81,7 +81,8 @@ public class TunerServiceImpl extends TunerService { private ContentResolver mContentResolver; private int mCurrentUser; - private CurrentUserTracker mUserTracker; + private UserTracker.Callback mCurrentUserTracker; + private UserTracker mUserTracker; /** */ @@ -91,11 +92,13 @@ public class TunerServiceImpl extends TunerService { @Main Handler mainHandler, LeakDetector leakDetector, DemoModeController demoModeController, - BroadcastDispatcher broadcastDispatcher) { + BroadcastDispatcher broadcastDispatcher, + UserTracker userTracker) { mContext = context; mContentResolver = mContext.getContentResolver(); mLeakDetector = leakDetector; mDemoModeController = demoModeController; + mUserTracker = userTracker; for (UserInfo user : UserManager.get(mContext).getUsers()) { mCurrentUser = user.getUserHandle().getIdentifier(); @@ -104,21 +107,22 @@ public class TunerServiceImpl extends TunerService { } } - mCurrentUser = ActivityManager.getCurrentUser(); - mUserTracker = new CurrentUserTracker(broadcastDispatcher) { + mCurrentUser = mUserTracker.getUserId(); + mCurrentUserTracker = new UserTracker.Callback() { @Override - public void onUserSwitched(int newUserId) { - mCurrentUser = newUserId; + public void onUserChanged(int newUser, Context userContext) { + mCurrentUser = newUser; reloadAll(); reregisterAll(); } }; - mUserTracker.startTracking(); + mUserTracker.addCallback(mCurrentUserTracker, + new HandlerExecutor(mainHandler)); } @Override public void destroy() { - mUserTracker.stopTracking(); + mUserTracker.removeCallback(mCurrentUserTracker); } private void upgradeTuner(int oldVersion, int newVersion, Handler mainHandler) { @@ -137,7 +141,7 @@ public class TunerServiceImpl extends TunerService { } } if (oldVersion < 2) { - setTunerEnabled(mContext, false); + setTunerEnabled(mContext, mUserTracker.getUserHandle(), false); } // 3 Removed because of a revert. if (oldVersion < 4) { @@ -272,7 +276,7 @@ public class TunerServiceImpl extends TunerService { @Override public void onChange(boolean selfChange, java.util.Collection<Uri> uris, int flags, int userId) { - if (userId == ActivityManager.getCurrentUser()) { + if (userId == mUserTracker.getUserId()) { for (Uri u : uris) { reloadSetting(u); } diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java index f277b30e589d..bde88b1b5533 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java @@ -17,7 +17,7 @@ package com.android.systemui.tv; import com.android.systemui.dagger.GlobalRootComponent; -import com.android.systemui.pip.tv.dagger.TvPipModule; +import com.android.systemui.wmshell.TvPipModule; import dagger.Binds; import dagger.Module; diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java index eb8f065149c8..a6cd350b33ce 100644 --- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java +++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java @@ -23,7 +23,6 @@ import android.view.InflateException; import android.view.LayoutInflater; import android.view.View; -import com.android.keyguard.KeyguardMessageArea; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.qs.QSFooterImpl; import com.android.systemui.qs.QSPanel; @@ -108,11 +107,6 @@ public class InjectionInflationController { NotificationStackScrollLayout createNotificationStackScrollLayout(); /** - * Creates the KeyguardMessageArea. - */ - KeyguardMessageArea createKeyguardMessageArea(); - - /** * Creates the QSPanel. */ QSPanel createQSPanel(); diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java index 90b95ea9562e..0d63324966fd 100644 --- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java +++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java @@ -25,6 +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 java.util.Arrays; @@ -34,8 +35,8 @@ public class NotificationChannels extends SystemUI { public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP"; public static String GENERAL = "GEN"; public static String STORAGE = "DSK"; - public static String TVPIP = "TPP"; public static String BATTERY = "BAT"; + public static String TVPIP = PipNotification.NOTIFICATION_CHANNEL_TVPIP; public static String HINTS = "HNT"; public NotificationChannels(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/util/ViewController.java b/packages/SystemUI/src/com/android/systemui/util/ViewController.java index 64f8dbbb9e34..c7aa780fcacb 100644 --- a/packages/SystemUI/src/com/android/systemui/util/ViewController.java +++ b/packages/SystemUI/src/com/android/systemui/util/ViewController.java @@ -23,7 +23,20 @@ import android.view.View.OnAttachStateChangeListener; * Utility class that handles view lifecycle events for View Controllers. * * Implementations should handle setup and teardown related activities inside of - * {@link #onViewAttached()} and {@link #onViewDetached()}. + * {@link #onViewAttached()} and {@link #onViewDetached()}. Be sure to call {@link #init()} on + * any child controllers that this uses. This can be done in {@link init()} if the controllers + * are injected, or right after creation time of the child controller. + * + * Tip: View "attachment" happens top down - parents are notified that they are attached before + * any children. That means that if you call a method on a child controller in + * {@link #onViewAttached()}, the child controller may not have had its onViewAttach method + * called, so it may not be fully set up. + * + * As such, make sure that methods on your controller are safe to call _before_ its {@link #init()} + * and {@link #onViewAttached()} methods are called. Specifically, if your controller must call + * {@link View#findViewById(int)} on its root view to setup member variables, do so in its + * constructor. Save {@link #onViewAttached()} for things that can happen post-construction - adding + * listeners, dynamically changing content, or other runtime decisions. * * @param <T> View class that this ViewController is for. */ @@ -54,10 +67,12 @@ public abstract class ViewController<T extends View> { } mInited = true; - if (mView.isAttachedToWindow()) { - mOnAttachStateListener.onViewAttachedToWindow(mView); + if (mView != null) { + if (mView.isAttachedToWindow()) { + mOnAttachStateListener.onViewAttachedToWindow(mView); + } + mView.addOnAttachStateChangeListener(mOnAttachStateListener); } - mView.addOnAttachStateChangeListener(mOnAttachStateListener); } /** diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt index 3347cf6ca2a4..603d423143ce 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt @@ -46,6 +46,7 @@ class TransitionLayout @JvmOverloads constructor( private var measureAsConstraint: Boolean = false private var currentState: TransitionViewState = TransitionViewState() private var updateScheduled = false + private var isPreDrawApplicatorRegistered = false private var desiredMeasureWidth = 0 private var desiredMeasureHeight = 0 @@ -74,6 +75,7 @@ class TransitionLayout @JvmOverloads constructor( override fun onPreDraw(): Boolean { updateScheduled = false viewTreeObserver.removeOnPreDrawListener(this) + isPreDrawApplicatorRegistered = false applyCurrentState() return true } @@ -94,6 +96,14 @@ class TransitionLayout @JvmOverloads constructor( } } + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + if (isPreDrawApplicatorRegistered) { + viewTreeObserver.removeOnPreDrawListener(preDrawApplicator) + isPreDrawApplicatorRegistered = false + } + } + /** * Apply the current state to the view and its widgets */ @@ -158,7 +168,10 @@ class TransitionLayout @JvmOverloads constructor( private fun applyCurrentStateOnPredraw() { if (!updateScheduled) { updateScheduled = true - viewTreeObserver.addOnPreDrawListener(preDrawApplicator) + if (!isPreDrawApplicatorRegistered) { + viewTreeObserver.addOnPreDrawListener(preDrawApplicator) + isPreDrawApplicatorRegistered = true + } } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java index f094854308bc..247baf8f3a42 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java @@ -14,45 +14,38 @@ * limitations under the License. */ -package com.android.systemui.pip.tv.dagger; +package com.android.systemui.wmshell; -import android.app.Activity; import android.content.Context; +import android.os.Handler; +import android.view.LayoutInflater; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.pip.Pip; -import com.android.systemui.pip.PipBoundsHandler; -import com.android.systemui.pip.PipSurfaceTransactionHelper; -import com.android.systemui.pip.PipTaskOrganizer; -import com.android.systemui.pip.PipUiEventLogger; -import com.android.systemui.pip.tv.PipController; -import com.android.systemui.pip.tv.PipMenuActivity; -import com.android.systemui.pip.tv.PipNotification; -import com.android.systemui.wmshell.WindowManagerShellWrapper; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.pip.PipBoundsHandler; +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.splitscreen.SplitScreen; import java.util.Optional; -import dagger.Binds; import dagger.Module; import dagger.Provides; -import dagger.multibindings.ClassKey; -import dagger.multibindings.IntoMap; /** * Dagger module for TV Pip. */ -@Module(subcomponents = {TvPipComponent.class}) +@Module public abstract class TvPipModule { - /** Inject into PipMenuActivity. */ - @Binds - @IntoMap - @ClassKey(PipMenuActivity.class) - public abstract Activity providePipMenuActivity(PipMenuActivity activity); - @SysUISingleton @Provides static Pip providePipController(Context context, @@ -65,6 +58,21 @@ public abstract class TvPipModule { @SysUISingleton @Provides + static PipControlsViewController providePipControlsViewContrller( + PipControlsView pipControlsView, PipController pipController, + LayoutInflater layoutInflater, Handler handler) { + return new PipControlsViewController(pipControlsView, pipController, layoutInflater, + handler); + } + + @SysUISingleton + @Provides + static PipControlsView providePipControlsView(Context context) { + return new PipControlsView(context, null); + } + + @SysUISingleton + @Provides static PipNotification providePipNotification(Context context, PipController pipController) { return new PipNotification(context, pipController); @@ -72,13 +80,13 @@ public abstract class TvPipModule { @SysUISingleton @Provides - static PipBoundsHandler providesPipBoundsHandler(Context context) { + static PipBoundsHandler providePipBoundsHandler(Context context) { return new PipBoundsHandler(context); } @SysUISingleton @Provides - static PipTaskOrganizer providesPipTaskOrganizer(Context context, + static PipTaskOrganizer providePipTaskOrganizer(Context context, PipBoundsHandler pipBoundsHandler, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<SplitScreen> splitScreenOptional, DisplayController displayController, diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java index d6595b2323bf..56efffc29d85 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java @@ -22,15 +22,17 @@ import android.view.IWindowManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.pip.tv.dagger.TvPipModule; 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.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; +import java.util.concurrent.Executor; + import dagger.Module; import dagger.Provides; @@ -44,16 +46,18 @@ public class TvWMShellModule { @SysUISingleton @Provides static DisplayImeController provideDisplayImeController(IWindowManager wmService, - DisplayController displayController, @Main Handler mainHandler, + DisplayController displayController, @Main Executor mainExecutor, TransactionPool transactionPool) { - return new DisplayImeController(wmService, displayController, mainHandler, transactionPool); + return new DisplayImeController(wmService, displayController, mainExecutor, + transactionPool); } static SplitScreen provideSplitScreen(Context context, DisplayController displayController, SystemWindows systemWindows, DisplayImeController displayImeController, @Main Handler handler, - TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer) { + TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, + SyncTransactionQueue syncQueue) { return new SplitScreenController(context, displayController, systemWindows, - displayImeController, handler, transactionPool, shellTaskOrganizer); + displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue); } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 250c59262c4d..b82853562049 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -55,8 +55,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.pip.Pip; -import com.android.systemui.pip.phone.PipUtils; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.TaskStackChangeListener; @@ -73,6 +71,8 @@ import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedEvents; import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; +import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.pip.phone.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogImpl; import com.android.wm.shell.splitscreen.SplitScreen; diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index bf9df8e2f89e..970d5001172e 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -27,21 +27,25 @@ import com.android.internal.logging.UiEventLogger; import com.android.systemui.bubbles.Bubbles; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.pip.Pip; -import com.android.systemui.pip.PipSurfaceTransactionHelper; -import com.android.systemui.pip.PipUiEventLogger; -import com.android.systemui.pip.phone.PipAppOpsListener; -import com.android.systemui.pip.phone.PipMediaController; -import com.android.systemui.pip.phone.PipTouchHandler; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.util.DeviceConfigProxy; -import com.android.systemui.util.FloatingContentCoordinator; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.animation.FlingAnimationUtils; +import com.android.wm.shell.common.AnimationThread; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.HandlerExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.onehanded.OneHanded; +import com.android.wm.shell.pip.Pip; +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.PipMediaController; +import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.splitscreen.SplitScreen; import dagger.BindsOptionalOf; @@ -88,7 +92,7 @@ public abstract class WMShellBaseModule { @SysUISingleton @Provides - static PipAppOpsListener providesPipAppOpsListener(Context context, + static PipAppOpsListener providePipAppOpsListener(Context context, IActivityManager activityManager, PipTouchHandler pipTouchHandler) { return new PipAppOpsListener(context, activityManager, pipTouchHandler.getMotionHelper()); @@ -96,7 +100,7 @@ public abstract class WMShellBaseModule { @SysUISingleton @Provides - static PipMediaController providesPipMediaController(Context context, + static PipMediaController providePipMediaController(Context context, IActivityManager activityManager) { return new PipMediaController(context, activityManager); } @@ -123,8 +127,17 @@ public abstract class WMShellBaseModule { @SysUISingleton @Provides - static ShellTaskOrganizer provideShellTaskOrganizer(TransactionPool transactionPool) { - ShellTaskOrganizer organizer = new ShellTaskOrganizer(transactionPool); + static SyncTransactionQueue provideSyncTransactionQueue(@Main Handler handler, + TransactionPool pool) { + return new SyncTransactionQueue(pool, handler); + } + + @SysUISingleton + @Provides + static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue, + @Main Handler handler, TransactionPool transactionPool) { + ShellTaskOrganizer organizer = new ShellTaskOrganizer(syncQueue, transactionPool, + new HandlerExecutor(handler), AnimationThread.instance().getExecutor()); organizer.registerOrganizer(); return organizer; } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java index 6ed836c3b954..61c3f9c3616f 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java @@ -22,28 +22,31 @@ import android.view.IWindowManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.pip.Pip; -import com.android.systemui.pip.PipBoundsHandler; -import com.android.systemui.pip.PipSurfaceTransactionHelper; -import com.android.systemui.pip.PipTaskOrganizer; -import com.android.systemui.pip.PipUiEventLogger; -import com.android.systemui.pip.phone.PipAppOpsListener; -import com.android.systemui.pip.phone.PipController; -import com.android.systemui.pip.phone.PipMediaController; -import com.android.systemui.pip.phone.PipMenuActivityController; -import com.android.systemui.pip.phone.PipTouchHandler; -import com.android.systemui.util.FloatingContentCoordinator; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.pip.PipBoundsHandler; +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.phone.PipAppOpsListener; +import com.android.wm.shell.pip.phone.PipController; +import com.android.wm.shell.pip.phone.PipMediaController; +import com.android.wm.shell.pip.phone.PipMenuActivityController; +import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import java.util.Optional; +import java.util.concurrent.Executor; import dagger.Module; import dagger.Provides; @@ -58,9 +61,10 @@ public class WMShellModule { @SysUISingleton @Provides static DisplayImeController provideDisplayImeController(IWindowManager wmService, - DisplayController displayController, @Main Handler mainHandler, + DisplayController displayController, @Main Executor mainExecutor, TransactionPool transactionPool) { - return new DisplayImeController(wmService, displayController, mainHandler, transactionPool); + return new DisplayImeController(wmService, displayController, mainExecutor, + transactionPool); } @SysUISingleton @@ -84,9 +88,10 @@ public class WMShellModule { static SplitScreen provideSplitScreen(Context context, DisplayController displayController, SystemWindows systemWindows, DisplayImeController displayImeController, @Main Handler handler, - TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer) { + TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, + SyncTransactionQueue syncQueue) { return new SplitScreenController(context, displayController, systemWindows, - displayImeController, handler, transactionPool, shellTaskOrganizer); + displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue); } @SysUISingleton diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java index 9be2d124026c..dffad6ccbea5 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java @@ -41,8 +41,6 @@ import android.testing.TestableLooper.RunWithLooper; import android.testing.ViewUtils; import android.view.SurfaceControlViewHost; import android.view.SurfaceView; -import android.view.ViewGroup; -import android.widget.FrameLayout; import androidx.test.filters.SmallTest; @@ -67,7 +65,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { private ComponentName mComponentName; private Intent mServiceIntent; private TestableLooper mTestableLooper; - private ViewGroup mParent; + private KeyguardSecurityContainer mKeyguardSecurityContainer; @Mock private Handler mHandler; @@ -84,8 +82,8 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); - mParent = spy(new FrameLayout(mContext)); - ViewUtils.attachView(mParent); + mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext)); + ViewUtils.attachView(mKeyguardSecurityContainer); mTestableLooper = TestableLooper.get(this); mComponentName = new ComponentName(mContext, "FakeKeyguardClient.class"); @@ -96,13 +94,14 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { when(mKeyguardClient.queryLocalInterface(anyString())).thenReturn(mKeyguardClient); when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient); - mTestController = new AdminSecondaryLockScreenController( - mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler); + mTestController = new AdminSecondaryLockScreenController.Factory( + mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler) + .create(mKeyguardCallback); } @After public void tearDown() { - ViewUtils.detachView(mParent); + ViewUtils.detachView(mKeyguardSecurityContainer); } @Test @@ -146,7 +145,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { SurfaceView v = verifySurfaceReady(); mTestController.hide(); - verify(mParent).removeView(v); + verify(mKeyguardSecurityContainer).removeView(v); assertThat(mContext.isBound(mComponentName)).isFalse(); } @@ -154,7 +153,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { public void testHide_notShown() throws Exception { mTestController.hide(); // Nothing should happen if trying to hide when the view isn't attached yet. - verify(mParent, never()).removeView(any(SurfaceView.class)); + verify(mKeyguardSecurityContainer, never()).removeView(any(SurfaceView.class)); } @Test @@ -182,7 +181,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { private SurfaceView verifySurfaceReady() throws Exception { mTestableLooper.processAllMessages(); ArgumentCaptor<SurfaceView> captor = ArgumentCaptor.forClass(SurfaceView.class); - verify(mParent).addView(captor.capture()); + verify(mKeyguardSecurityContainer).addView(captor.capture()); mTestableLooper.processAllMessages(); verify(mKeyguardClient).onCreateKeyguardSurface(any(), any(IKeyguardCallback.class)); @@ -190,7 +189,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { } private void verifyViewDismissed(SurfaceView v) throws Exception { - verify(mParent).removeView(v); + verify(mKeyguardSecurityContainer).removeView(v); verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID, true); assertThat(mContext.isBound(mComponentName)).isFalse(); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java new file mode 100644 index 000000000000..c2ade81a9877 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.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 com.android.keyguard; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; +import android.view.KeyEvent; + +import androidx.test.filters.SmallTest; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { + + @Mock + private KeyguardAbsKeyInputView mAbsKeyInputView; + @Mock + private PasswordTextView mPasswordEntry; + @Mock + private KeyguardMessageArea mKeyguardMessageArea; + @Mock + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + private SecurityMode mSecurityMode; + @Mock + private LockPatternUtils mLockPatternUtils; + @Mock + private KeyguardSecurityCallback mKeyguardSecurityCallback; + @Mock + private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory; + @Mock + private KeyguardMessageAreaController mKeyguardMessageAreaController; + @Mock + private LatencyTracker mLatencyTracker; + + private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class))) + .thenReturn(mKeyguardMessageAreaController); + when(mAbsKeyInputView.getPasswordTextViewId()).thenReturn(1); + when(mAbsKeyInputView.findViewById(1)).thenReturn(mPasswordEntry); + when(mAbsKeyInputView.isAttachedToWindow()).thenReturn(true); + when(mAbsKeyInputView.findViewById(R.id.keyguard_message_area)) + .thenReturn(mKeyguardMessageArea); + mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView, + mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, + mKeyguardMessageAreaControllerFactory, mLatencyTracker) { + @Override + void resetState() { + } + + @Override + public void onResume(int reason) { + super.onResume(reason); + } + }; + mKeyguardAbsKeyInputViewController.init(); + reset(mKeyguardMessageAreaController); // Clear out implicit call to init. + } + + @Test + public void onKeyDown_clearsSecurityMessage() { + ArgumentCaptor<KeyDownListener> onKeyDownListenerArgumentCaptor = + ArgumentCaptor.forClass(KeyDownListener.class); + verify(mAbsKeyInputView).setKeyDownListener(onKeyDownListenerArgumentCaptor.capture()); + onKeyDownListenerArgumentCaptor.getValue().onKeyDown( + KeyEvent.KEYCODE_0, mock(KeyEvent.class)); + verify(mKeyguardSecurityCallback).userActivity(); + verify(mKeyguardMessageAreaController).setMessage(eq("")); + } + + @Test + public void onKeyDown_noSecurityMessageInteraction() { + ArgumentCaptor<KeyDownListener> onKeyDownListenerArgumentCaptor = + ArgumentCaptor.forClass(KeyDownListener.class); + verify(mAbsKeyInputView).setKeyDownListener(onKeyDownListenerArgumentCaptor.capture()); + onKeyDownListenerArgumentCaptor.getValue().onKeyDown( + KeyEvent.KEYCODE_UNKNOWN, mock(KeyEvent.class)); + verifyZeroInteractions(mKeyguardSecurityCallback); + verifyZeroInteractions(mKeyguardMessageAreaController); + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 5999e2cdec78..e7930795c7f8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -41,11 +41,9 @@ import android.widget.FrameLayout; import android.widget.TextClock; import com.android.systemui.R; -import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.util.InjectionInflationController; import org.junit.Before; import org.junit.Test; @@ -78,12 +76,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area)) .thenReturn(mMockKeyguardSliceView); - InjectionInflationController inflationController = new InjectionInflationController( - SystemUIFactory.getInstance() - .getSysUIComponent() - .createViewInstanceCreatorFactory()); - LayoutInflater layoutInflater = inflationController - .injectable(LayoutInflater.from(getContext())); + LayoutInflater layoutInflater = LayoutInflater.from(getContext()); layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() { @Override diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java index 54e879e2ff38..64632afe9bfa 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java @@ -16,8 +16,10 @@ package com.android.keyguard; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.media.AudioManager; import android.telephony.TelephonyManager; @@ -47,13 +49,15 @@ public class KeyguardHostViewControllerTest extends SysuiTestCase { @Mock private KeyguardHostView mKeyguardHostView; @Mock - private KeyguardSecurityContainerController mKeyguardSecurityContainerController; - @Mock private AudioManager mAudioManager; @Mock private TelephonyManager mTelephonyManager; @Mock private ViewMediatorCallback mViewMediatorCallback; + @Mock + KeyguardSecurityContainerController.Factory mKeyguardSecurityContainerControllerFactory; + @Mock + private KeyguardSecurityContainerController mKeyguardSecurityContainerController; @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -62,9 +66,12 @@ public class KeyguardHostViewControllerTest extends SysuiTestCase { @Before public void setup() { + when(mKeyguardSecurityContainerControllerFactory.create(any( + KeyguardSecurityContainer.SecurityCallback.class))) + .thenReturn(mKeyguardSecurityContainerController); mKeyguardHostViewController = new KeyguardHostViewController( - mKeyguardHostView, mKeyguardUpdateMonitor, mKeyguardSecurityContainerController, - mAudioManager, mTelephonyManager, mViewMediatorCallback); + mKeyguardHostView, mKeyguardUpdateMonitor, mAudioManager, mTelephonyManager, + mViewMediatorCallback, mKeyguardSecurityContainerControllerFactory); } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java new file mode 100644 index 000000000000..a7197cca530c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.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.keyguard; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class KeyguardMessageAreaControllerTest extends SysuiTestCase { + @Mock + private ConfigurationController mConfigurationController; + @Mock + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + private KeyguardMessageArea mKeyguardMessageArea; + + private KeyguardMessageAreaController mMessageAreaController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mMessageAreaController = new KeyguardMessageAreaController.Factory( + mKeyguardUpdateMonitor, mConfigurationController).create(mKeyguardMessageArea); + } + + @Test + public void onAttachedToWindow_registersConfigurationCallback() { + ArgumentCaptor<ConfigurationListener> configurationListenerArgumentCaptor = + ArgumentCaptor.forClass(ConfigurationListener.class); + + mMessageAreaController.onViewAttached(); + verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture()); + + mMessageAreaController.onViewDetached(); + verify(mConfigurationController).removeCallback( + eq(configurationListenerArgumentCaptor.getValue())); + } + + @Test + public void onAttachedToWindow_registersKeyguardUpdateMontiorCallback() { + ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + + mMessageAreaController.onViewAttached(); + verify(mKeyguardUpdateMonitor).registerCallback( + keyguardUpdateMonitorCallbackArgumentCaptor.capture()); + + mMessageAreaController.onViewDetached(); + verify(mKeyguardUpdateMonitor).removeCallback( + eq(keyguardUpdateMonitorCallbackArgumentCaptor.getValue())); + } + + @Test + public void testClearsTextField() { + mMessageAreaController.setMessage(""); + verify(mKeyguardMessageArea).setMessage(""); + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java index fc7b9a4b47d1..31fb25a7a89c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 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. @@ -11,65 +11,60 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ package com.android.keyguard; -import static junit.framework.Assert.assertEquals; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; +import static com.google.common.truth.Truth.assertThat; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import android.view.View; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.policy.ConfigurationController; 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) @RunWithLooper public class KeyguardMessageAreaTest extends SysuiTestCase { - @Mock - private ConfigurationController mConfigurationController; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private KeyguardMessageArea mMessageArea; + private KeyguardMessageArea mKeyguardMessageArea; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mMessageArea = new KeyguardMessageArea(mContext, null, mKeyguardUpdateMonitor, - mConfigurationController); - waitForIdleSync(); + mKeyguardMessageArea = new KeyguardMessageArea(mContext, null); + mKeyguardMessageArea.setBouncerVisible(true); } @Test - public void onAttachedToWindow_registersConfigurationCallback() { - mMessageArea.onAttachedToWindow(); - verify(mConfigurationController).addCallback(eq(mMessageArea)); - - mMessageArea.onDetachedFromWindow(); - verify(mConfigurationController).removeCallback(eq(mMessageArea)); + public void testShowsTextField() { + mKeyguardMessageArea.setVisibility(View.INVISIBLE); + mKeyguardMessageArea.setMessage("oobleck"); + assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck"); } @Test - public void clearFollowedByMessage_keepsMessage() { - mMessageArea.setMessage(""); - mMessageArea.setMessage("test"); - - CharSequence[] messageText = new CharSequence[1]; - messageText[0] = mMessageArea.getText(); - - assertEquals("test", messageText[0]); + public void testHiddenWhenBouncerHidden() { + mKeyguardMessageArea.setBouncerVisible(false); + mKeyguardMessageArea.setVisibility(View.INVISIBLE); + mKeyguardMessageArea.setMessage("oobleck"); + assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE); + assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck"); } + @Test + public void testClearsTextField() { + mKeyguardMessageArea.setVisibility(View.VISIBLE); + mKeyguardMessageArea.setMessage(""); + assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE); + assertThat(mKeyguardMessageArea.getText()).isEqualTo(""); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt new file mode 100644 index 000000000000..c69ec1a254c3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.util.LatencyTracker +import com.android.internal.widget.LockPatternUtils +import com.android.internal.widget.LockPatternView +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class KeyguardPatternViewControllerTest : SysuiTestCase() { + @Mock + private lateinit var mKeyguardPatternView: KeyguardPatternView + @Mock + private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock + private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode + @Mock + private lateinit var mLockPatternUtils: LockPatternUtils + @Mock + private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback + @Mock + private lateinit var mLatencyTracker: LatencyTracker + @Mock + private lateinit + var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory + @Mock + private lateinit var mKeyguardMessageArea: KeyguardMessageArea + @Mock + private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController + @Mock + private lateinit var mLockPatternView: LockPatternView + + private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true) + `when`(mKeyguardPatternView.findViewById<KeyguardMessageArea>(R.id.keyguard_message_area)) + .thenReturn(mKeyguardMessageArea) + `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView)) + .thenReturn(mLockPatternView) + `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea)) + .thenReturn(mKeyguardMessageAreaController) + mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView, + mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, + mLatencyTracker, mKeyguardMessageAreaControllerFactory) + } + + @Test + fun onPause_clearsTextField() { + mKeyguardPatternViewController.init() + mKeyguardPatternViewController.onPause() + verify(mKeyguardMessageAreaController).setMessage("") + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt deleted file mode 100644 index b4363cf215f1..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.keyguard - -import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.view.LayoutInflater - -import com.android.systemui.R -import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.policy.ConfigurationController -import com.google.common.truth.Truth.assertThat - -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.mock - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper -class KeyguardPatternViewTest : SysuiTestCase() { - - private lateinit var mKeyguardPatternView: KeyguardPatternView - private lateinit var mSecurityMessage: KeyguardMessageArea - - @Before - fun setup() { - val inflater = LayoutInflater.from(context) - mDependency.injectMockDependency(KeyguardUpdateMonitor::class.java) - mKeyguardPatternView = inflater.inflate(R.layout.keyguard_pattern_view, null) - as KeyguardPatternView - mSecurityMessage = KeyguardMessageArea(mContext, null, - mock(KeyguardUpdateMonitor::class.java), mock(ConfigurationController::class.java)) - mKeyguardPatternView.mSecurityMessageDisplay = mSecurityMessage - } - - @Test - fun onPause_clearsTextField() { - mSecurityMessage.setMessage("an old message") - mKeyguardPatternView.onPause() - assertThat(mSecurityMessage.text).isEqualTo("") - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java new file mode 100644 index 000000000000..4944284698a0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -0,0 +1,106 @@ +/* + * 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.keyguard; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +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) +@RunWithLooper +public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { + + @Mock + private KeyguardPinBasedInputView mPinBasedInputView; + @Mock + private PasswordTextView mPasswordEntry; + @Mock + private KeyguardMessageArea mKeyguardMessageArea; + @Mock + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + private SecurityMode mSecurityMode; + @Mock + private LockPatternUtils mLockPatternUtils; + @Mock + private KeyguardSecurityCallback mKeyguardSecurityCallback; + @Mock + private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory; + @Mock + private KeyguardMessageAreaController mKeyguardMessageAreaController; + @Mock + private LatencyTracker mLatencyTracker; + @Mock + private LiftToActivateListener mLiftToactivateListener; + @Mock + private View mDeleteButton; + @Mock + private View mOkButton; + + private KeyguardPinBasedInputViewController mKeyguardPinViewController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class))) + .thenReturn(mKeyguardMessageAreaController); + when(mPinBasedInputView.getPasswordTextViewId()).thenReturn(1); + when(mPinBasedInputView.findViewById(1)).thenReturn(mPasswordEntry); + when(mPinBasedInputView.isAttachedToWindow()).thenReturn(true); + when(mPinBasedInputView.findViewById(R.id.keyguard_message_area)) + .thenReturn(mKeyguardMessageArea); + when(mPinBasedInputView.findViewById(R.id.delete_button)) + .thenReturn(mDeleteButton); + when(mPinBasedInputView.findViewById(R.id.key_enter)) + .thenReturn(mOkButton); + mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView, + mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, + mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener) { + @Override + public void onResume(int reason) { + super.onResume(reason); + } + }; + mKeyguardPinViewController.init(); + } + + @Test + public void onResume_requestsFocus() { + mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON); + verify(mPasswordEntry).requestFocus(); + } +} + diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java deleted file mode 100644 index 6666a926c68b..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.keyguard; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; -import android.view.KeyEvent; -import android.view.LayoutInflater; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -public class KeyguardPinBasedInputViewTest extends SysuiTestCase { - - @Mock - private PasswordTextView mPasswordEntry; - @Mock - private SecurityMessageDisplay mSecurityMessageDisplay; - @InjectMocks - private KeyguardPinBasedInputView mKeyguardPinView; - - @Before - public void setup() { - LayoutInflater inflater = LayoutInflater.from(getContext()); - mDependency.injectMockDependency(KeyguardUpdateMonitor.class); - mKeyguardPinView = - (KeyguardPinBasedInputView) inflater.inflate(R.layout.keyguard_pin_view, null); - MockitoAnnotations.initMocks(this); - } - - @Test - public void onResume_requestsFocus() { - mKeyguardPinView.onResume(KeyguardSecurityView.SCREEN_ON); - verify(mPasswordEntry).requestFocus(); - } - - @Test - public void onKeyDown_clearsSecurityMessage() { - mKeyguardPinView.onKeyDown(KeyEvent.KEYCODE_0, mock(KeyEvent.class)); - verify(mSecurityMessageDisplay).setMessage(eq("")); - } - - @Test - public void onKeyDown_noSecurityMessageInteraction() { - mKeyguardPinView.onKeyDown(KeyEvent.KEYCODE_UNKNOWN, mock(KeyEvent.class)); - verifyZeroInteractions(mSecurityMessageDisplay); - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java index 559284ac0672..ae159c73b99f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java @@ -31,9 +31,7 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardDisplayManager.KeyguardPresentation; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.systemui.R; -import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; -import com.android.systemui.util.InjectionInflationController; import org.junit.After; import org.junit.Before; @@ -65,7 +63,6 @@ public class KeyguardPresentationTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mDependency.injectMockDependency(KeyguardUpdateMonitor.class); when(mMockKeyguardClockSwitch.getContext()).thenReturn(mContext); when(mMockKeyguardSliceView.getContext()).thenReturn(mContext); when(mMockKeyguardStatusView.getContext()).thenReturn(mContext); @@ -77,11 +74,7 @@ public class KeyguardPresentationTest extends SysuiTestCase { allowTestableLooperAsMainThread(); - InjectionInflationController inflationController = new InjectionInflationController( - SystemUIFactory.getInstance() - .getSysUIComponent() - .createViewInstanceCreatorFactory()); - mLayoutInflater = inflationController.injectable(LayoutInflater.from(mContext)); + mLayoutInflater = LayoutInflater.from(mContext); mLayoutInflater.setPrivateFactory(new LayoutInflater.Factory2() { @Override diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java new file mode 100644 index 000000000000..eef38d316775 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -0,0 +1,129 @@ +/* + * 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.keyguard; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.WindowInsetsController; + +import androidx.test.filters.SmallTest; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.policy.KeyguardStateController; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper() +public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { + + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + + @Mock + private KeyguardSecurityContainer mView; + @Mock + private AdminSecondaryLockScreenController.Factory mAdminSecondaryLockScreenControllerFactory; + @Mock + private AdminSecondaryLockScreenController mAdminSecondaryLockScreenController; + @Mock + private LockPatternUtils mLockPatternUtils; + @Mock + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + private KeyguardSecurityModel mKeyguardSecurityModel; + @Mock + private MetricsLogger mMetricsLogger; + @Mock + private UiEventLogger mUiEventLogger; + @Mock + private KeyguardStateController mKeyguardStateController; + @Mock + private KeyguardInputViewController mInputViewController; + @Mock + private KeyguardSecurityContainer.SecurityCallback mSecurityCallback; + @Mock + private WindowInsetsController mWindowInsetsController; + @Mock + private KeyguardSecurityViewFlipper mSecurityViewFlipper; + @Mock + private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController; + + private KeyguardSecurityContainerController mKeyguardSecurityContainerController; + + @Before + public void setup() { + when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class))) + .thenReturn(mAdminSecondaryLockScreenController); + when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); + + mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory( + mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, + mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, + mKeyguardStateController, mKeyguardSecurityViewFlipperController) + .create(mSecurityCallback); + } + + @Test + public void showSecurityScreen_canInflateAllModes() { + SecurityMode[] modes = SecurityMode.values(); + for (SecurityMode mode : modes) { + when(mInputViewController.getSecurityMode()).thenReturn(mode); + mKeyguardSecurityContainerController.showSecurityScreen(mode); + if (mode == SecurityMode.Invalid) { + verify(mKeyguardSecurityViewFlipperController, never()).getSecurityView( + any(SecurityMode.class), any(KeyguardSecurityCallback.class)); + } else { + verify(mKeyguardSecurityViewFlipperController).getSecurityView( + eq(mode), any(KeyguardSecurityCallback.class)); + } + } + } + + @Test + public void startDisappearAnimation_animatesKeyboard() { + when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( + SecurityMode.Password); + when(mInputViewController.getSecurityMode()).thenReturn( + SecurityMode.Password); + when(mKeyguardSecurityViewFlipperController.getSecurityView( + eq(SecurityMode.Password), any(KeyguardSecurityCallback.class))) + .thenReturn(mInputViewController); + mKeyguardSecurityContainerController.showPrimarySecurityScreen(false /* turningOff */); + + mKeyguardSecurityContainerController.startDisappearAnimation(null); + verify(mInputViewController).startDisappearAnimation(eq(null)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index a867825e223d..854be1f76722 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -19,23 +19,19 @@ package com.android.keyguard; import static android.view.WindowInsets.Type.ime; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.view.LayoutInflater; import android.view.WindowInsetsController; import androidx.test.filters.SmallTest; -import com.android.systemui.R; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; import org.junit.Rule; @@ -50,68 +46,26 @@ import org.mockito.junit.MockitoRule; @TestableLooper.RunWithLooper() public class KeyguardSecurityContainerTest extends SysuiTestCase { - @Mock - private KeyguardSecurityModel mKeyguardSecurityModel; - @Mock - private KeyguardStateController mKeyguardStateController; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - private KeyguardSecurityContainer.SecurityCallback mSecurityCallback; - @Mock - private KeyguardSecurityView mSecurityView; + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + @Mock private WindowInsetsController mWindowInsetsController; @Mock private KeyguardSecurityViewFlipper mSecurityViewFlipper; - @Rule - public MockitoRule mRule = MockitoJUnit.rule(); + private KeyguardSecurityContainer mKeyguardSecurityContainer; @Before public void setup() { - mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController); - mDependency.injectTestDependency(KeyguardSecurityModel.class, mKeyguardSecurityModel); - mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor); - mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext()) { - @Override - protected KeyguardSecurityView getSecurityView( - KeyguardSecurityModel.SecurityMode securityMode) { - return mSecurityView; - } - }; - mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper; when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); - mKeyguardSecurityContainer.setSecurityCallback(mSecurityCallback); - } - - @Test - public void showSecurityScreen_canInflateAllModes() { - Context context = getContext(); - - for (int theme : new int[] {R.style.Theme_SystemUI, R.style.Theme_SystemUI_Light}) { - context.setTheme(theme); - final LayoutInflater inflater = LayoutInflater.from(context); - KeyguardSecurityModel.SecurityMode[] modes = - KeyguardSecurityModel.SecurityMode.values(); - for (KeyguardSecurityModel.SecurityMode mode : modes) { - final int resId = mKeyguardSecurityContainer.getLayoutIdFor(mode); - if (resId == 0) { - continue; - } - inflater.inflate(resId, null /* root */, false /* attach */); - } - } + mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext()); + mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper; } @Test public void startDisappearAnimation_animatesKeyboard() { - when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( - KeyguardSecurityModel.SecurityMode.Password); - mKeyguardSecurityContainer.showPrimarySecurityScreen(false /* turningOff */); - - mKeyguardSecurityContainer.startDisappearAnimation(null); - verify(mSecurityView).startDisappearAnimation(eq(null)); + mKeyguardSecurityContainer.startDisappearAnimation(SecurityMode.Password); verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(), any(), any()); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java new file mode 100644 index 000000000000..3b7f4b839853 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java @@ -0,0 +1,102 @@ +/* + * 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.keyguard; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.view.WindowInsetsController; + +import androidx.test.filters.SmallTest; + +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper() +public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { + + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + + @Mock + private KeyguardSecurityViewFlipper mView; + @Mock + private LayoutInflater mLayoutInflater; + @Mock + private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory; + @Mock + private KeyguardInputViewController mKeyguardInputViewController; + @Mock + private KeyguardInputView mInputView; + @Mock + private WindowInsetsController mWindowInsetsController; + @Mock + private KeyguardSecurityCallback mKeyguardSecurityCallback; + + private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController; + + @Before + public void setup() { + when(mKeyguardSecurityViewControllerFactory.create( + any(KeyguardInputView.class), any(SecurityMode.class), + any(KeyguardSecurityCallback.class))) + .thenReturn(mKeyguardInputViewController); + when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController); + + mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView, + mLayoutInflater, mKeyguardSecurityViewControllerFactory); + } + + @Test + public void showSecurityScreen_canInflateAllModes() { + SecurityMode[] modes = SecurityMode.values(); + // Always return an invalid controller so that we're always making a new one. + when(mKeyguardInputViewController.getSecurityMode()).thenReturn(SecurityMode.Invalid); + for (SecurityMode mode : modes) { + reset(mLayoutInflater); + when(mLayoutInflater.inflate(anyInt(), eq(mView), eq(false))) + .thenReturn(mInputView); + mKeyguardSecurityViewFlipperController.getSecurityView(mode, mKeyguardSecurityCallback); + if (mode == SecurityMode.Invalid || mode == SecurityMode.None) { + verify(mLayoutInflater, never()).inflate( + anyInt(), any(ViewGroup.class), anyBoolean()); + } else { + verify(mLayoutInflater).inflate(anyInt(), eq(mView), eq(false)); + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java index 0431704778c3..79ec4f2c553a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java @@ -24,9 +24,7 @@ import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; import com.android.systemui.R; -import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; -import com.android.systemui.util.InjectionInflationController; import org.junit.Before; import org.junit.Test; @@ -50,13 +48,7 @@ public class KeyguardStatusViewTest extends SysuiTestCase { @Before public void setUp() { allowTestableLooperAsMainThread(); - mDependency.injectMockDependency(KeyguardUpdateMonitor.class); - InjectionInflationController inflationController = new InjectionInflationController( - SystemUIFactory.getInstance() - .getSysUIComponent() - .createViewInstanceCreatorFactory()); - LayoutInflater layoutInflater = inflationController - .injectable(LayoutInflater.from(getContext())); + LayoutInflater layoutInflater = LayoutInflater.from(getContext()); mKeyguardStatusView = (KeyguardStatusView) layoutInflater.inflate(R.layout.keyguard_status_view, null); org.mockito.MockitoAnnotations.initMocks(this); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index d93cc05c57db..e73ed801b7df 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -53,6 +53,7 @@ import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorProperties; +import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; import android.media.AudioManager; import android.os.Bundle; @@ -132,7 +133,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private FaceManager mFaceManager; @Mock - private List<FaceSensorProperties> mFaceSensorProperties; + private List<FaceSensorPropertiesInternal> mFaceSensorProperties; @Mock private BiometricManager mBiometricManager; @Mock @@ -177,13 +178,14 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mFaceManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.hasEnrolledTemplates()).thenReturn(true); when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); - when(mFaceManager.getSensorProperties()).thenReturn(mFaceSensorProperties); + when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties); // IBiometricsFace@1.0 does not support detection, only authentication. when(mFaceSensorProperties.isEmpty()).thenReturn(false); - when(mFaceSensorProperties.get(anyInt())).thenReturn(new FaceSensorProperties(0 /* id */, - false /* supportsFaceDetection */, true /* supportsSelfIllumination */, - 1 /* maxTemplatesAllowed */)); + when(mFaceSensorProperties.get(anyInt())).thenReturn(new FaceSensorPropertiesInternal( + 0 /* id */, + FaceSensorProperties.STRENGTH_STRONG, 1 /* maxTemplatesAllowed */, + false /* supportsFaceDetection */, true /* supportsSelfIllumination */)); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index d107f646bb6b..ab805f42ab56 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -62,6 +62,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R.dimen; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.settings.UserTracker; import com.android.systemui.tuner.TunerService; import org.junit.Before; @@ -88,6 +89,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { private TunerService mTunerService; @Mock private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private UserTracker mUserTracker; @Before public void setup() { @@ -109,7 +112,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { mContext.addMockSystemService(DisplayManager.class, mDisplayManager); mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler, - mBroadcastDispatcher, mTunerService) { + mBroadcastDispatcher, mTunerService, mUserTracker) { @Override public void start() { super.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 539b321991ad..c6440f4290fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -17,10 +17,17 @@ package com.android.systemui.accessibility; import static android.view.Choreographer.FrameCallback; +import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItems; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; @@ -37,17 +44,20 @@ import android.view.Surface; import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; +import android.view.accessibility.AccessibilityNodeInfo; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -71,6 +81,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { private Resources mResources; private WindowMagnificationController mWindowMagnificationController; private Instrumentation mInstrumentation; + private View mMirrorView; @Before public void setUp() { @@ -83,12 +94,16 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { ).when(mWindowManager).getMaximumWindowMetrics(); mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); doAnswer(invocation -> { - View view = invocation.getArgument(0); + mMirrorView = invocation.getArgument(0); WindowManager.LayoutParams lp = invocation.getArgument(1); - view.setLayoutParams(lp); + mMirrorView.setLayoutParams(lp); return null; }).when(mWindowManager).addView(any(View.class), any(WindowManager.LayoutParams.class)); doAnswer(invocation -> { + mMirrorView = null; + return null; + }).when(mWindowManager).removeView(any(View.class)); + doAnswer(invocation -> { FrameCallback callback = invocation.getArgument(0); callback.doFrame(0); return null; @@ -147,14 +162,18 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { } @Test - public void setScale_enabled_expectedValue() { + public void setScale_enabled_expectedValueAndUpdateStateDescription() { mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + () -> mWindowMagnificationController.enableWindowMagnification(2.0f, Float.NaN, Float.NaN)); mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f)); assertEquals(3.0f, mWindowMagnificationController.getScale(), 0); + ArgumentCaptor<Runnable> runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mHandler).postDelayed(runnableArgumentCaptor.capture(), anyLong()); + runnableArgumentCaptor.getValue().run(); + assertThat(mMirrorView.getStateDescription().toString(), containsString("300")); } @Test @@ -227,4 +246,52 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt()); } + + @Test + public void initializeA11yNode_enabled_expectedValues() { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.enableWindowMagnification(2.5f, Float.NaN, + Float.NaN); + }); + assertNotNull(mMirrorView); + final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); + + mMirrorView.onInitializeAccessibilityNodeInfo(nodeInfo); + + assertNotNull(nodeInfo.getContentDescription()); + assertThat(nodeInfo.getStateDescription().toString(), containsString("250")); + assertThat(nodeInfo.getActionList(), + hasItems(new AccessibilityAction(R.id.accessibility_action_zoom_in, null), + new AccessibilityAction(R.id.accessibility_action_zoom_out, null), + new AccessibilityAction(R.id.accessibility_action_move_right, null), + new AccessibilityAction(R.id.accessibility_action_move_left, null), + new AccessibilityAction(R.id.accessibility_action_move_down, null), + new AccessibilityAction(R.id.accessibility_action_move_up, null))); + } + + @Test + public void performA11yActions_visible_expectedResults() { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.enableWindowMagnification(2.5f, Float.NaN, + Float.NaN); + }); + assertNotNull(mMirrorView); + + assertTrue( + mMirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null)); + // Minimum scale is 2.0. + assertEquals(2.0f, mWindowMagnificationController.getScale(), 0f); + + assertTrue(mMirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null)); + assertEquals(3.0f, mWindowMagnificationController.getScale(), 0f); + + // TODO: Verify the final state when the mirror surface is visible. + assertTrue(mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null)); + assertTrue( + mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null)); + assertTrue( + mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null)); + assertTrue( + mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index f6b39c2ca38b..e0420ca38f45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -49,6 +49,7 @@ import android.hardware.biometrics.SensorProperties; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Bundle; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -120,14 +121,15 @@ public class AuthControllerTest extends SysuiTestCase { when(mDialog2.isAllowDeviceCredentials()).thenReturn(false); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); - FingerprintSensorProperties prop = new FingerprintSensorProperties(1 /* sensorId */, + FingerprintSensorPropertiesInternal prop = new FingerprintSensorPropertiesInternal( + 1 /* sensorId */, SensorProperties.STRENGTH_STRONG, 1 /* maxEnrollmentsPerUser */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, true /* resetLockoutRequireHardwareAuthToken */); - List<FingerprintSensorProperties> props = new ArrayList<>(); + List<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); props.add(prop); - when(mFingerprintManager.getSensorProperties()).thenReturn(props); + when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props); mAuthController = new TestableAuthController(context, mCommandQueue, mStatusBarStateController, mActivityTaskManager, mFingerprintManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 9b9f840e5383..99e39b8097eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.biometrics; +import static junit.framework.Assert.assertEquals; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.eq; @@ -25,7 +27,10 @@ import static org.mockito.Mockito.when; import android.content.res.Resources; import android.content.res.TypedArray; +import android.hardware.biometrics.SensorProperties; import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.FingerprintSensorProperties; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.PowerManager; import android.os.RemoteException; @@ -54,11 +59,18 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.ArrayList; +import java.util.List; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper public class UdfpsControllerTest extends SysuiTestCase { + // Use this for inputs going into SystemUI. Use UdfpsController.mUdfpsSensorId for things + // leaving SystemUI. + private static final int TEST_UDFPS_SENSOR_ID = 1; + @Rule public MockitoRule rule = MockitoJUnit.rule(); @@ -98,6 +110,13 @@ public class UdfpsControllerTest extends SysuiTestCase { public void setUp() { setUpResources(); when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)).thenReturn(mUdfpsView); + final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); + props.add(new FingerprintSensorPropertiesInternal(TEST_UDFPS_SENSOR_ID, + SensorProperties.STRENGTH_STRONG, + 5 /* maxEnrollmentsPerUser */, + FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, + true /* resetLockoutRequiresHardwareAuthToken */)); + when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props); mSystemSettings = new FakeSettings(); mFgExecutor = new FakeExecutor(new FakeSystemClock()); mUdfpsController = new UdfpsController( @@ -112,6 +131,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); + + assertEquals(TEST_UDFPS_SENSOR_ID, mUdfpsController.mUdfpsSensorId); } private void setUpResources() { @@ -138,15 +159,15 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void showUdfpsOverlay_addsViewToWindow() throws RemoteException { - mOverlayController.showUdfpsOverlay(); + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID); mFgExecutor.runAllReady(); verify(mWindowManager).addView(eq(mUdfpsView), any()); } @Test public void hideUdfpsOverlay_removesViewFromWindow() throws RemoteException { - mOverlayController.showUdfpsOverlay(); - mOverlayController.hideUdfpsOverlay(); + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID); + mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID); mFgExecutor.runAllReady(); verify(mWindowManager).removeView(eq(mUdfpsView)); } @@ -156,7 +177,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the bouncer is showing mUdfpsController.setBouncerVisibility(/* isShowing */ true); // WHEN a request to show the overlay is received - mOverlayController.showUdfpsOverlay(); + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID); mFgExecutor.runAllReady(); // THEN the overlay is not attached verify(mWindowManager, never()).addView(eq(mUdfpsView), any()); @@ -165,7 +186,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void setBouncerVisibility_overlayDetached() throws RemoteException { // GIVEN that the overlay has been requested - mOverlayController.showUdfpsOverlay(); + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID); // WHEN the bouncer becomes visible mUdfpsController.setBouncerVisibility(/* isShowing */ true); mFgExecutor.runAllReady(); @@ -178,7 +199,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the bouncer is visible mUdfpsController.setBouncerVisibility(/* isShowing */ true); // AND the overlay has been requested - mOverlayController.showUdfpsOverlay(); + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID); // WHEN the bouncer is closed mUdfpsController.setBouncerVisibility(/* isShowing */ false); mFgExecutor.runAllReady(); @@ -193,7 +214,7 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mUdfpsView.isValidTouch(anyFloat(), anyFloat(), anyFloat())).thenReturn(true); // GIVEN that the overlay is showing - mOverlayController.showUdfpsOverlay(); + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID); mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); @@ -201,7 +222,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); event.recycle(); // THEN the event is passed to the FingerprintManager - verify(mFingerprintManager).onFingerDown(eq(0), eq(0), eq(0f), eq(0f)); + verify(mFingerprintManager).onFingerDown(eq(mUdfpsController.mUdfpsSensorId), eq(0), eq(0), + eq(0f), eq(0f)); // AND the scrim and dot is shown verify(mUdfpsView).showScrimAndDot(); } @@ -209,12 +231,13 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterrupt() throws RemoteException { // GIVEN that the overlay is showing - mOverlayController.showUdfpsOverlay(); + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID); mFgExecutor.runAllReady(); // WHEN fingerprint is requested because of AOD interrupt mUdfpsController.onAodInterrupt(0, 0); // THEN the event is passed to the FingerprintManager - verify(mFingerprintManager).onFingerDown(eq(0), eq(0), anyFloat(), anyFloat()); + verify(mFingerprintManager).onFingerDown(eq(mUdfpsController.mUdfpsSensorId), eq(0), eq(0), + anyFloat(), anyFloat()); // AND the scrim and dot is shown verify(mUdfpsView).showScrimAndDot(); } @@ -222,7 +245,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void cancelAodInterrupt() throws RemoteException { // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(); + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0); // WHEN it is cancelled @@ -234,7 +257,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterruptTimeout() throws RemoteException { // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(); + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0); // WHEN it times out diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 2f8d3f6a0722..08ccd854714a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -91,8 +91,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.FloatingContentCoordinator; -import com.android.systemui.wmshell.WindowManagerShellWrapper; +import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.common.FloatingContentCoordinator; import com.google.common.collect.ImmutableList; diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index cfbd398ec6a4..1eaa6a4b8742 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -90,9 +90,9 @@ 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.FloatingContentCoordinator; import com.android.systemui.util.InjectionInflationController; -import com.android.systemui.wmshell.WindowManagerShellWrapper; +import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.common.FloatingContentCoordinator; import org.junit.Before; import org.junit.Ignore; diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java index ec9571a14997..87ea22a94862 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java @@ -35,8 +35,9 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.util.FloatingContentCoordinator; -import com.android.systemui.wmshell.WindowManagerShellWrapper; +import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.common.FloatingContentCoordinator; + /** * Testable BubbleController subclass that immediately synchronizes surfaces. diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java index cc62a2f36392..9242ce940bcd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java @@ -33,7 +33,7 @@ import androidx.dynamicanimation.animation.SpringForce; import androidx.test.filters.SmallTest; import com.android.systemui.R; -import com.android.systemui.util.FloatingContentCoordinator; +import com.android.wm.shell.common.FloatingContentCoordinator; import org.junit.Before; import org.junit.Ignore; diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt index bb003ee59978..0a81c38e7448 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt @@ -27,6 +27,7 @@ import android.service.controls.IControlsSubscription import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -61,6 +62,8 @@ class ControlsBindingControllerImplTest : SysuiTestCase() { @Mock private lateinit var mockControlsController: ControlsController + @Mock(stubOnly = true) + private lateinit var mockUserTracker: UserTracker @Captor private lateinit var subscriberCaptor: ArgumentCaptor<IControlsSubscriber.Stub> @@ -82,9 +85,10 @@ class ControlsBindingControllerImplTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) providers.clear() + `when`(mockUserTracker.userHandle).thenReturn(user) controller = TestableControlsBindingControllerImpl( - mContext, executor, Lazy { mockControlsController }) + mContext, executor, Lazy { mockControlsController }, mockUserTracker) } @After @@ -364,8 +368,9 @@ class ControlsBindingControllerImplTest : SysuiTestCase() { class TestableControlsBindingControllerImpl( context: Context, executor: DelayableExecutor, - lazyController: Lazy<ControlsController> -) : ControlsBindingControllerImpl(context, executor, lazyController) { + lazyController: Lazy<ControlsController>, + userTracker: UserTracker +) : ControlsBindingControllerImpl(context, executor, lazyController, userTracker) { companion object { val providers = mutableListOf<ControlsProviderLifecycleManager>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index 45262c7788f1..f6c836a24f21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dump.DumpManager +import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import org.junit.After @@ -82,6 +83,8 @@ class ControlsControllerImplTest : SysuiTestCase() { private lateinit var broadcastDispatcher: BroadcastDispatcher @Mock private lateinit var listingController: ControlsListingController + @Mock(stubOnly = true) + private lateinit var userTracker: UserTracker @Captor private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo> @@ -143,6 +146,8 @@ class ControlsControllerImplTest : SysuiTestCase() { Settings.Secure.putIntForUser(mContext.contentResolver, ControlsControllerImpl.CONTROLS_AVAILABLE, 1, otherUser) + `when`(userTracker.userHandle).thenReturn(UserHandle.of(user)) + delayableExecutor = FakeExecutor(FakeSystemClock()) val wrapper = object : ContextWrapper(mContext) { @@ -162,7 +167,8 @@ class ControlsControllerImplTest : SysuiTestCase() { listingController, broadcastDispatcher, Optional.of(persistenceWrapper), - mock(DumpManager::class.java) + mock(DumpManager::class.java), + userTracker ) controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper @@ -217,7 +223,8 @@ class ControlsControllerImplTest : SysuiTestCase() { listingController, broadcastDispatcher, Optional.of(persistenceWrapper), - mock(DumpManager::class.java) + mock(DumpManager::class.java), + userTracker ) assertEquals(listOf(TEST_STRUCTURE_INFO), controller_other.getFavorites()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt index 841b2553ebda..db41d8d37a43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt @@ -26,6 +26,7 @@ import androidx.test.filters.SmallTest import com.android.settingslib.applications.ServiceListing import com.android.systemui.SysuiTestCase import com.android.systemui.controls.ControlsServiceInfo +import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import org.junit.After @@ -66,6 +67,8 @@ class ControlsListingControllerImplTest : SysuiTestCase() { private lateinit var serviceInfo: ServiceInfo @Mock private lateinit var serviceInfo2: ServiceInfo + @Mock(stubOnly = true) + private lateinit var userTracker: UserTracker private var componentName = ComponentName("pkg1", "class1") private var componentName2 = ComponentName("pkg2", "class2") @@ -86,6 +89,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() { `when`(serviceInfo.componentName).thenReturn(componentName) `when`(serviceInfo2.componentName).thenReturn(componentName2) + `when`(userTracker.userId).thenReturn(user) val wrapper = object : ContextWrapper(mContext) { override fun createContextAsUser(user: UserHandle, flags: Int): Context { @@ -93,7 +97,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() { } } - controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }) + controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }, userTracker) verify(mockSL).addCallback(capture(serviceListingCallbackCaptor)) } @@ -121,7 +125,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() { `when`(mockServiceListing.reload()).then { callback?.onServicesReloaded(listOf(serviceInfo)) } - ControlsListingControllerImpl(mContext, exec, { mockServiceListing }) + ControlsListingControllerImpl(mContext, exec, { mockServiceListing }, userTracker) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 0e376bd356a2..2d460aa0c9c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -192,6 +192,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { public void onItemClick_clickDevice_verifyConnectDevice() { assertThat(mMediaDevice2.getState()).isEqualTo( LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); mViewHolder.mFrameLayout.performClick(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt index 1f10d013222d..cb17829ddaf8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt @@ -16,15 +16,14 @@ package com.android.systemui.privacy -import android.os.UserManager import android.provider.DeviceConfig import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.internal.config.sysui.SystemUiDeviceConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.appops.AppOpsController -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dump.DumpManager +import com.android.systemui.settings.UserTracker import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.DeviceConfigProxyFake import com.android.systemui.util.concurrency.FakeExecutor @@ -62,11 +61,9 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { @Mock private lateinit var callback: PrivacyItemController.Callback @Mock - private lateinit var userManager: UserManager - @Mock - private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock private lateinit var dumpManager: DumpManager + @Mock + private lateinit var userTracker: UserTracker private lateinit var privacyItemController: PrivacyItemController private lateinit var executor: FakeExecutor @@ -77,9 +74,8 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { appOpsController, executor, executor, - broadcastDispatcher, deviceConfigProxy, - userManager, + userTracker, dumpManager ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index 0a079b148f5d..16a11050f9bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -18,10 +18,8 @@ package com.android.systemui.privacy import android.app.ActivityManager import android.app.AppOpsManager -import android.content.Intent import android.content.pm.UserInfo import android.os.UserHandle -import android.os.UserManager import android.provider.DeviceConfig import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -30,8 +28,8 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.appops.AppOpItem import com.android.systemui.appops.AppOpsController -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dump.DumpManager +import com.android.systemui.settings.UserTracker import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.DeviceConfigProxyFake import com.android.systemui.util.concurrency.FakeExecutor @@ -52,6 +50,7 @@ import org.mockito.ArgumentMatchers.anyList import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.`when` import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.doReturn import org.mockito.Mockito.mock @@ -84,9 +83,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Mock private lateinit var callback: PrivacyItemController.Callback @Mock - private lateinit var userManager: UserManager - @Mock - private lateinit var broadcastDispatcher: BroadcastDispatcher + private lateinit var userTracker: UserTracker @Mock private lateinit var dumpManager: DumpManager @Captor @@ -103,9 +100,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { appOpsController, executor, executor, - broadcastDispatcher, deviceConfigProxy, - userManager, + userTracker, dumpManager ) } @@ -119,11 +115,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { // Listen to everything by default changeAll(true) - doReturn(listOf(object : UserInfo() { - init { - id = CURRENT_USER_ID - } - })).`when`(userManager).getProfiles(anyInt()) + `when`(userTracker.userProfiles).thenReturn(listOf(UserInfo(CURRENT_USER_ID, "", 0))) privacyItemController = PrivacyItemController() } @@ -163,37 +155,26 @@ class PrivacyItemControllerTest : SysuiTestCase() { } @Test - fun testRegisterReceiver_allUsers() { + fun testRegisterCallback() { privacyItemController.addCallback(callback) executor.runAllReady() - verify(broadcastDispatcher, atLeastOnce()).registerReceiver( - eq(privacyItemController.userSwitcherReceiver), any(), eq(null), eq(UserHandle.ALL)) - verify(broadcastDispatcher, never()) - .unregisterReceiver(eq(privacyItemController.userSwitcherReceiver)) - } - - @Test - fun testReceiver_ACTION_USER_FOREGROUND() { - privacyItemController.userSwitcherReceiver.onReceive(context, - Intent(Intent.ACTION_USER_SWITCHED)) - executor.runAllReady() - verify(userManager).getProfiles(anyInt()) + verify(userTracker, atLeastOnce()).addCallback( + eq(privacyItemController.userTrackerCallback), any()) + verify(userTracker, never()).removeCallback(eq(privacyItemController.userTrackerCallback)) } @Test - fun testReceiver_ACTION_MANAGED_PROFILE_ADDED() { - privacyItemController.userSwitcherReceiver.onReceive(context, - Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)) + fun testCallback_userChanged() { + privacyItemController.userTrackerCallback.onUserChanged(0, mContext) executor.runAllReady() - verify(userManager).getProfiles(anyInt()) + verify(userTracker).userProfiles } @Test - fun testReceiver_ACTION_MANAGED_PROFILE_REMOVED() { - privacyItemController.userSwitcherReceiver.onReceive(context, - Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) + fun testReceiver_profilesChanged() { + privacyItemController.userTrackerCallback.onProfilesChanged(emptyList()) executor.runAllReady() - verify(userManager).getProfiles(anyInt()) + verify(userTracker).userProfiles } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java index d4688d77a840..99f2d8042547 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java @@ -14,30 +14,40 @@ package com.android.systemui.qs; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.ClipData; +import android.content.ClipboardManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; import android.view.View; +import android.widget.TextView; import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.R.id; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.utils.leaks.LeakCheckedTest; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -47,19 +57,46 @@ public class QSFooterImplTest extends LeakCheckedTest { private QSFooterImpl mFooter; private ActivityStarter mActivityStarter; private DeviceProvisionedController mDeviceProvisionedController; + private UserInfoController mUserInfoController; + private UserTracker mUserTracker; + @Mock + private ClipboardManager mClipboardManager; @Before public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class); mDeviceProvisionedController = mDependency.injectMockDependency( DeviceProvisionedController.class); + mUserInfoController = mDependency.injectMockDependency(UserInfoController.class); + mUserTracker = mDependency.injectMockDependency(UserTracker.class); + + mContext.addMockSystemService(ClipboardManager.class, mClipboardManager); + + when(mUserTracker.getUserContext()).thenReturn(mContext); + TestableLooper.get(this).runWithLooper( () -> mFooter = (QSFooterImpl) LayoutInflater.from(mContext).inflate( R.layout.qs_footer_impl, null)); } @Test + public void testBuildTextCopy() { + TextView buildTextView = mFooter.requireViewById(R.id.build); + CharSequence buildText = "TEST"; + buildTextView.setText(buildText); + buildTextView.setLongClickable(true); + + buildTextView.performLongClick(); + + ArgumentCaptor<ClipData> captor = ArgumentCaptor.forClass(ClipData.class); + verify(mClipboardManager).setPrimaryClip(captor.capture()); + assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(buildText); + } + + @Test @Ignore("failing") public void testSettings_UserNotSetup() { View settingsButton = mFooter.findViewById(id.settings_button); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index b46c6ef81fa7..1c2d08402e69 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -45,6 +45,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSFactoryImpl; +import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoTileManager; @@ -107,7 +108,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { mock(PluginManager.class), mock(TunerService.class), () -> mock(AutoTileManager.class), mock(DumpManager.class), mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)), - mock(QSLogger.class), mock(UiEventLogger.class)); + mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class)); qs.setHost(host); qs.setListening(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java index cb3a04862eb7..4b7a26870308 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java @@ -38,7 +38,6 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.testing.UiEventLoggerFake; -import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -49,7 +48,7 @@ import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.customize.QSCustomizer; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.util.animation.DisappearParameters; import com.android.systemui.util.animation.UniqueObjectHostView; @@ -93,11 +92,9 @@ public class QSPanelTest extends SysuiTestCase { @Mock private MediaHost mMediaHost; @Mock - private LocalBluetoothManager mLocalBluetoothManager; - @Mock private ActivityStarter mActivityStarter; - @Mock - private NotificationEntryManager mEntryManager; + @Mock(stubOnly = true) + private UserTracker mUserTracker; private UiEventLoggerFake mUiEventLogger; @Before @@ -117,7 +114,7 @@ public class QSPanelTest extends SysuiTestCase { mTestableLooper.runWithLooper(() -> { mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class); mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher, - mQSLogger, mMediaHost, mUiEventLogger); + mQSLogger, mMediaHost, mUiEventLogger, mUserTracker); mQsPanel.onFinishInflate(); // Provides a parent with non-zero size for QSPanel mParentView = new FrameLayout(mContext); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java index 417b19f0cfc1..fd1866b9ebc3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java @@ -17,7 +17,6 @@ package com.android.systemui.qs; import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -25,7 +24,6 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.UserInfo; -import android.os.UserManager; import android.provider.Settings; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -42,6 +40,7 @@ import android.widget.TextView; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.SecurityController; import org.junit.Before; @@ -73,20 +72,20 @@ public class QSSecurityFooterTest extends SysuiTestCase { private TestableImageView mFooterIcon; private QSSecurityFooter mFooter; private SecurityController mSecurityController = mock(SecurityController.class); - private UserManager mUserManager; + private UserTracker mUserTracker; @Before public void setUp() { mDependency.injectTestDependency(SecurityController.class, mSecurityController); mDependency.injectTestDependency(Dependency.BG_LOOPER, TestableLooper.get(this).getLooper()); + mUserTracker = mock(UserTracker.class); + when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class)); mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, new LayoutInflaterBuilder(mContext) .replace("ImageView", TestableImageView.class) .build()); - mUserManager = Mockito.mock(UserManager.class); - mContext.addMockSystemService(Context.USER_SERVICE, mUserManager); - mFooter = new QSSecurityFooter(null, mContext); + mFooter = new QSSecurityFooter(null, mContext, mUserTracker); mRootView = (ViewGroup) mFooter.getView(); mFooterText = mRootView.findViewById(R.id.footer_text); mFooterIcon = mRootView.findViewById(R.id.footer_icon); @@ -141,7 +140,7 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.getDeviceOwnerOrganizationName()).thenReturn(null); final UserInfo mockUserInfo = Mockito.mock(UserInfo.class); when(mockUserInfo.isDemo()).thenReturn(true); - when(mUserManager.getUserInfo(anyInt())).thenReturn(mockUserInfo); + when(mUserTracker.getUserInfo()).thenReturn(mockUserInfo); Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1); mFooter.refreshState(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index c8e1a74d969f..452ff4af7c15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -55,6 +55,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.StatusBar; @@ -110,6 +111,8 @@ public class QSTileHostTest extends SysuiTestCase { private CustomTile mCustomTile; @Mock private UiEventLogger mUiEventLogger; + @Mock + private UserTracker mUserTracker; private Handler mHandler; private TestableLooper mLooper; @@ -122,7 +125,7 @@ public class QSTileHostTest extends SysuiTestCase { mHandler = new Handler(mLooper.getLooper()); mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler, mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager, - mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger); + mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker); setUpTileFactory(); Settings.Secure.putStringForUser(mContext.getContentResolver(), QSTileHost.TILES_SETTING, @@ -301,10 +304,10 @@ public class QSTileHostTest extends SysuiTestCase { PluginManager pluginManager, TunerService tunerService, Provider<AutoTileManager> autoTiles, DumpManager dumpManager, BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, UserTracker userTracker) { super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager, tunerService, autoTiles, dumpManager, broadcastDispatcher, - Optional.of(statusBar), qsLogger, uiEventLogger); + Optional.of(statusBar), qsLogger, uiEventLogger, userTracker); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index 2ef7c65acb0b..a2ffb8409c1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -55,6 +55,7 @@ import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.settings.UserTracker; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -103,6 +104,8 @@ public class TileQueryHelperTest extends SysuiTestCase { private QSTileHost mQSTileHost; @Mock private PackageManager mPackageManager; + @Mock + private UserTracker mUserTracker; @Captor private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor; @@ -133,7 +136,7 @@ public class TileQueryHelperTest extends SysuiTestCase { FakeSystemClock clock = new FakeSystemClock(); mMainExecutor = new FakeExecutor(clock); mBgExecutor = new FakeExecutor(clock); - mTileQueryHelper = new TileQueryHelper(mContext, mMainExecutor, mBgExecutor); + mTileQueryHelper = new TileQueryHelper(mContext, mUserTracker, mMainExecutor, mBgExecutor); mTileQueryHelper.setListener(mListener); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java index 683e8f4a2511..6a9d9fa6d5a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java @@ -22,11 +22,13 @@ import static junit.framework.Assert.assertTrue; import android.content.ComponentName; import android.os.Handler; import android.os.HandlerThread; +import android.os.UserHandle; import android.test.suitebuilder.annotation.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; +import com.android.systemui.settings.UserTracker; import org.junit.After; import org.junit.Before; @@ -44,6 +46,7 @@ public class TileServiceManagerTest extends SysuiTestCase { private HandlerThread mThread; private Handler mHandler; private TileServiceManager mTileServiceManager; + private UserTracker mUserTracker; @Before public void setUp() throws Exception { @@ -51,13 +54,18 @@ public class TileServiceManagerTest extends SysuiTestCase { mThread.start(); mHandler = Handler.createAsync(mThread.getLooper()); mTileServices = Mockito.mock(TileServices.class); + mUserTracker = Mockito.mock(UserTracker.class); + Mockito.when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM); + Mockito.when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM); + Mockito.when(mTileServices.getContext()).thenReturn(mContext); mTileLifecycle = Mockito.mock(TileLifecycleManager.class); Mockito.when(mTileLifecycle.isActiveTile()).thenReturn(false); ComponentName componentName = new ComponentName(mContext, TileServiceManagerTest.class); Mockito.when(mTileLifecycle.getComponent()).thenReturn(componentName); - mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mTileLifecycle); + mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker, + mTileLifecycle); } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 53ed4cf12b2f..2a3bc31cb2a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -46,6 +46,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSFactoryImpl; +import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.StatusBar; @@ -92,6 +93,8 @@ public class TileServicesTest extends SysuiTestCase { private QSLogger mQSLogger; @Mock private UiEventLogger mUiEventLogger; + @Mock + private UserTracker mUserTracker; @Before public void setUp() throws Exception { @@ -110,8 +113,10 @@ public class TileServicesTest extends SysuiTestCase { mock(BroadcastDispatcher.class), Optional.of(mStatusBar), mQSLogger, - mUiEventLogger); - mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher); + mUiEventLogger, + mUserTracker); + mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher, + mUserTracker); } @After @@ -186,8 +191,8 @@ public class TileServicesTest extends SysuiTestCase { private class TestTileServices extends TileServices { TestTileServices(QSTileHost host, Looper looper, - BroadcastDispatcher broadcastDispatcher) { - super(host, looper, broadcastDispatcher); + BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) { + super(host, looper, broadcastDispatcher, userTracker); } @Override 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 727f91c589df..0c3db57f1a2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java @@ -36,11 +36,11 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.pip.Pip; 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.splitscreen.SplitScreen; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt new file mode 100644 index 000000000000..16eb1d9dbca1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt @@ -0,0 +1,94 @@ +/* + * 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.commandline + +import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper + +import com.android.systemui.SysuiTestCase + +import org.mockito.ArgumentMatchers.anyList +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +import java.io.PrintWriter +import java.io.StringWriter +import java.util.concurrent.Executor + +private fun <T> anyObject(): T { + return Mockito.anyObject<T>() +} + +private fun <T : Any> safeEq(value: T): T = eq(value) ?: value + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +@SmallTest +class CommandRegistryTest : SysuiTestCase() { + lateinit var registry: CommandRegistry + val inLineExecutor = object : Executor { + override fun execute(command: Runnable) { + command.run() + } + } + + val writer: PrintWriter = PrintWriter(StringWriter()) + + @Before + fun setup() { + registry = CommandRegistry(context, inLineExecutor) + } + + @Test(expected = IllegalStateException::class) + fun testRegisterCommand_throwsWhenAlreadyRegistered() { + registry.registerCommand(COMMAND) { FakeCommand() } + // Should throw when registering the same command twice + registry.registerCommand(COMMAND) { FakeCommand() } + } + + @Test + fun testOnShellCommand() { + var fakeCommand = mock(Command::class.java) + registry.registerCommand(COMMAND) { fakeCommand } + registry.onShellCommand(writer, arrayOf(COMMAND)) + verify(fakeCommand).execute(anyObject(), anyList()) + } + + @Test + fun testArgsPassedToShellCommand() { + var fakeCommand = mock(Command::class.java) + registry.registerCommand(COMMAND) { fakeCommand } + registry.onShellCommand(writer, arrayOf(COMMAND, "arg1", "arg2", "arg3")) + verify(fakeCommand).execute(anyObject(), safeEq(listOf("arg1", "arg2", "arg3"))) + } + + class FakeCommand() : Command { + override fun execute(pw: PrintWriter, args: List<String>) { + } + + override fun help(pw: PrintWriter) { + } + } +} + +private const val COMMAND = "test_command" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 23b12d4e115f..a6ea9966a0f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -93,7 +93,6 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.recents.Recents; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; @@ -232,7 +231,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private KeyguardLiftController mKeyguardLiftController; @Mock private VolumeComponent mVolumeComponent; @Mock private CommandQueue mCommandQueue; - @Mock private Recents mRecents; @Mock private Provider<StatusBarComponent.Builder> mStatusBarComponentBuilderProvider; @Mock private StatusBarComponent.Builder mStatusBarComponentBuilder; @Mock private StatusBarComponent mStatusBarComponent; @@ -392,7 +390,6 @@ public class StatusBarTest extends SysuiTestCase { mDozeScrimController, mVolumeComponent, mCommandQueue, - Optional.of(mRecents), mStatusBarComponentBuilderProvider, mPluginManager, Optional.of(mSplitScreen), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java index 0c2361a9f6b9..be836d4132f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java @@ -20,11 +20,13 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.content.Intent; import android.location.LocationManager; import android.os.Handler; +import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -35,6 +37,7 @@ import com.android.systemui.BootCompleteCache; import com.android.systemui.SysuiTestCase; import com.android.systemui.appops.AppOpsController; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback; import org.junit.Before; @@ -52,10 +55,13 @@ public class LocationControllerImplTest extends SysuiTestCase { private TestableLooper mTestableLooper; @Mock private AppOpsController mAppOpsController; + @Mock private UserTracker mUserTracker; @Before public void setup() { MockitoAnnotations.initMocks(this); + when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM); + when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM); mTestableLooper = TestableLooper.get(this); mLocationController = spy(new LocationControllerImpl(mContext, @@ -63,7 +69,8 @@ public class LocationControllerImplTest extends SysuiTestCase { mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), mock(BroadcastDispatcher.class), - mock(BootCompleteCache.class))); + mock(BootCompleteCache.class), + mUserTracker)); mTestableLooper.processAllMessages(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index e88b514ef238..4fb85ad1bb4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -17,9 +17,15 @@ package com.android.systemui.statusbar.policy; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + import android.app.ActivityManager; import android.app.PendingIntent; import android.app.RemoteInput; +import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ShortcutManager; @@ -130,8 +136,18 @@ public class RemoteInputViewTest extends SysuiTestCase { private UserHandle getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser) throws Exception { + /** + * RemoteInputView, Icon, and Bubble have the situation need to handle the other user. + * SystemUI cross multiple user but this test(com.android.systemui.tests) doesn't cross + * multiple user. It needs some of mocking multiple user environment to ensure the + * createContextAsUser without throwing IllegalStateException. + */ + Context contextSpy = spy(mContext); + doReturn(contextSpy).when(contextSpy).createContextAsUser(any(), anyInt()); + doReturn(toUser.getIdentifier()).when(contextSpy).getUserId(); + NotificationTestHelper helper = new NotificationTestHelper( - mContext, + contextSpy, mDependency, TestableLooper.get(this)); ExpandableNotificationRow row = helper.createRow( diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt index 8eecde1f4f7c..31848a67698c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt @@ -5,6 +5,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.wm.shell.common.FloatingContentCoordinator import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse 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 0b7595000e3b..a5fbf195ed27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -36,8 +36,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.pip.Pip; -import com.android.systemui.pip.phone.PipTouchHandler; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.TaskStackChangeListener; @@ -49,6 +47,8 @@ import com.android.wm.shell.common.DisplayImeController; 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.splitscreen.SplitScreen; import org.junit.Before; @@ -70,6 +70,7 @@ public class WMShellTest extends SysuiTestCase { @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock ActivityManagerWrapper mActivityManagerWrapper; @Mock DisplayImeController mDisplayImeController; + @Mock InputConsumerController mMockInputConsumerController; @Mock NavigationModeController mNavigationModeController; @Mock ScreenLifecycle mScreenLifecycle; @Mock SysUiState mSysUiState; @@ -112,17 +113,22 @@ public class WMShellTest extends SysuiTestCase { @Test public void nonPipDevice_shouldNotInitPip() { - TestableContext spiedContext = spy(mContext); - when(mMockPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false); - when(spiedContext.getPackageManager()).thenReturn(mMockPackageManager); - final WMShell nonPipWMShell = new WMShell(spiedContext, mCommandQueue, - mConfigurationController, mInputConsumerController, mKeyguardUpdateMonitor, + final TestableContext nonPipContext = getNonPipFeatureContext(); + final WMShell nonPipWMShell = new WMShell(nonPipContext, mCommandQueue, + mConfigurationController, mMockInputConsumerController, mKeyguardUpdateMonitor, mActivityManagerWrapper, mDisplayImeController, mNavigationModeController, mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mSplitScreen), Optional.of(mOneHanded), mTaskOrganizer, mProtoTracer); nonPipWMShell.initPip(mPip); verify(mCommandQueue, never()).addCallback(any()); + verify(mKeyguardUpdateMonitor, never()).registerCallback(any()); + verify(mConfigurationController, never()).addCallback(any()); + verify(mSysUiState, never()).addCallback(any()); + verify(mActivityManagerWrapper, never()).registerTaskStackListener(any()); + verify(mMockInputConsumerController, never()).setInputListener(any()); + verify(mMockInputConsumerController, never()).setRegistrationListener(any()); + verify(mPip, never()).registerSessionListenerForCurrentUser(); } @Test @@ -151,4 +157,11 @@ public class WMShellTest extends SysuiTestCase { OneHandedGestureHandler.OneHandedGestureEventCallback.class)); verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class)); } + + TestableContext getNonPipFeatureContext() { + TestableContext spiedContext = spy(mContext); + when(mMockPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false); + when(spiedContext.getPackageManager()).thenReturn(mMockPackageManager); + return spiedContext; + } } diff --git a/packages/Tethering/src/android/net/ip/NeighborPacketForwarder.java b/packages/Tethering/src/android/net/ip/NeighborPacketForwarder.java index 73fc833fabf5..084743db03c4 100644 --- a/packages/Tethering/src/android/net/ip/NeighborPacketForwarder.java +++ b/packages/Tethering/src/android/net/ip/NeighborPacketForwarder.java @@ -25,7 +25,6 @@ import static android.system.OsConstants.SOCK_NONBLOCK; import static android.system.OsConstants.SOCK_RAW; import android.net.util.InterfaceParams; -import android.net.util.PacketReader; import android.net.util.SocketUtils; import android.net.util.TetheringUtils; import android.os.Handler; @@ -33,6 +32,8 @@ import android.system.ErrnoException; import android.system.Os; import android.util.Log; +import com.android.net.module.util.PacketReader; + import java.io.FileDescriptor; import java.io.IOException; import java.net.Inet6Address; diff --git a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java index 33b9d00e70dc..da5f25b2a596 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java @@ -28,6 +28,7 @@ import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; import android.net.netlink.NetlinkSocket; +import android.net.netlink.StructNfGenMsg; import android.net.netlink.StructNlMsgHdr; import android.net.util.SharedLog; import android.net.util.SocketUtils; @@ -41,11 +42,12 @@ import android.system.OsConstants; import com.android.internal.annotations.VisibleForTesting; import java.io.FileDescriptor; -import java.io.InterruptedIOException; import java.io.IOException; +import java.io.InterruptedIOException; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.NoSuchElementException; @@ -66,11 +68,12 @@ public class OffloadHardwareInterface { private static final String NO_IPV4_ADDRESS = ""; private static final String NO_IPV4_GATEWAY = ""; // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h - private static final int NF_NETLINK_CONNTRACK_NEW = 1; - private static final int NF_NETLINK_CONNTRACK_UPDATE = 2; - private static final int NF_NETLINK_CONNTRACK_DESTROY = 4; + public static final int NF_NETLINK_CONNTRACK_NEW = 1; + public static final int NF_NETLINK_CONNTRACK_UPDATE = 2; + public static final int NF_NETLINK_CONNTRACK_DESTROY = 4; // Reference libnetfilter_conntrack/linux_nfnetlink_conntrack.h public static final short NFNL_SUBSYS_CTNETLINK = 1; + public static final short IPCTNL_MSG_CT_NEW = 0; public static final short IPCTNL_MSG_CT_GET = 1; private final long NETLINK_MESSAGE_TIMEOUT_MS = 500; @@ -237,7 +240,7 @@ public class OffloadHardwareInterface { NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); if (h1 == null) return false; - sendNetlinkMessage(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET), + sendIpv4NfGenMsg(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET), (short) (NLM_F_REQUEST | NLM_F_DUMP)); final NativeHandle h2 = mDeps.createConntrackSocket( @@ -267,16 +270,23 @@ public class OffloadHardwareInterface { } @VisibleForTesting - public void sendNetlinkMessage(@NonNull NativeHandle handle, short type, short flags) { - final int length = StructNlMsgHdr.STRUCT_SIZE; + public void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) { + final int length = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; final byte[] msg = new byte[length]; - final StructNlMsgHdr nlh = new StructNlMsgHdr(); final ByteBuffer byteBuffer = ByteBuffer.wrap(msg); + byteBuffer.order(ByteOrder.nativeOrder()); + + final StructNlMsgHdr nlh = new StructNlMsgHdr(); nlh.nlmsg_len = length; nlh.nlmsg_type = type; nlh.nlmsg_flags = flags; - nlh.nlmsg_seq = 1; + nlh.nlmsg_seq = 0; nlh.pack(byteBuffer); + + // Header needs to be added to buffer since a generic netlink request is being sent. + final StructNfGenMsg nfh = new StructNfGenMsg((byte) OsConstants.AF_INET); + nfh.pack(byteBuffer); + try { NetlinkSocket.sendMessage(handle.getFileDescriptor(), msg, 0 /* offset */, length, NETLINK_MESSAGE_TIMEOUT_MS); diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java index 7dd5290ee83b..64d5025807e7 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -2104,7 +2104,7 @@ public class Tethering { } private boolean hasCallingPermission(@NonNull String permission) { - return mContext.checkCallingPermission(permission) == PERMISSION_GRANTED; + return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED; } /** Unregister tethering event callback */ diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index 64be2d9a5599..d206ea0b4d45 100644 --- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -553,7 +553,6 @@ public class EthernetTetheringTest { TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class); TestNetworkInterface iface = tnm.createTapInterface(); Log.d(TAG, "Created test interface " + iface.getInterfaceName()); - assertNotNull(NetworkInterface.getByName(iface.getInterfaceName())); return iface; } diff --git a/packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java new file mode 100644 index 000000000000..57c28fc67cc3 --- /dev/null +++ b/packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.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.networkstack.tethering; + +import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE; +import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_GET; +import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_NEW; +import static com.android.networkstack.tethering.OffloadHardwareInterface.NFNL_SUBSYS_CTNETLINK; +import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_DESTROY; +import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_NEW; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.net.netlink.StructNlMsgHdr; +import android.net.util.SharedLog; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.NativeHandle; +import android.system.Os; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConntrackSocketTest { + private static final long TIMEOUT = 500; + + private HandlerThread mHandlerThread; + private Handler mHandler; + private final SharedLog mLog = new SharedLog("privileged-test"); + + private OffloadHardwareInterface mOffloadHw; + private OffloadHardwareInterface.Dependencies mDeps; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mHandlerThread = new HandlerThread(getClass().getSimpleName()); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + + // Looper must be prepared here since AndroidJUnitRunner runs tests on separate threads. + if (Looper.myLooper() == null) Looper.prepare(); + + mDeps = new OffloadHardwareInterface.Dependencies(mLog); + mOffloadHw = new OffloadHardwareInterface(mHandler, mLog, mDeps); + } + + @Test + public void testIpv4ConntrackSocket() throws Exception { + // Set up server and connect. + final InetSocketAddress anyAddress = new InetSocketAddress( + InetAddress.getByName("127.0.0.1"), 0); + final ServerSocket serverSocket = new ServerSocket(); + serverSocket.bind(anyAddress); + final SocketAddress theAddress = serverSocket.getLocalSocketAddress(); + + // Make a connection to the server. + final Socket socket = new Socket(); + socket.connect(theAddress); + final Socket acceptedSocket = serverSocket.accept(); + + final NativeHandle handle = mDeps.createConntrackSocket( + NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); + mOffloadHw.sendIpv4NfGenMsg(handle, + (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET), + (short) (NLM_F_REQUEST | NLM_F_DUMP)); + + boolean foundConntrackEntry = false; + ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_RECV_BUFSIZE); + buffer.order(ByteOrder.nativeOrder()); + + try { + while (Os.read(handle.getFileDescriptor(), buffer) > 0) { + buffer.flip(); + + // TODO: ConntrackMessage should get a parse API like StructNlMsgHdr + // so we can confirm that the conntrack added is for the TCP connection above. + final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(buffer); + assertNotNull(nlmsghdr); + + // As long as 1 conntrack entry is found test case will pass, even if it's not + // the from the TCP connection above. + if (nlmsghdr.nlmsg_type == ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW)) { + foundConntrackEntry = true; + break; + } + } + } finally { + socket.close(); + serverSocket.close(); + } + assertTrue("Did not receive any NFNL_SUBSYS_CTNETLINK/IPCTNL_MSG_CT_NEW message", + foundConntrackEntry); + } +} diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java index c543fad62dba..38b19dd3da5c 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java @@ -17,8 +17,9 @@ package com.android.networkstack.tethering; import static android.net.util.TetheringUtils.uint16; -import static android.system.OsConstants.SOCK_STREAM; +import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_UNIX; +import static android.system.OsConstants.SOCK_STREAM; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -35,14 +36,15 @@ import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; +import android.net.netlink.StructNfGenMsg; import android.net.netlink.StructNlMsgHdr; import android.net.util.SharedLog; import android.os.Handler; import android.os.NativeHandle; import android.os.test.TestLooper; import android.system.ErrnoException; -import android.system.OsConstants; import android.system.Os; +import android.system.OsConstants; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -55,8 +57,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.FileDescriptor; -import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; @RunWith(AndroidJUnit4.class) @@ -218,7 +220,7 @@ public final class OffloadHardwareInterfaceTest { } @Test - public void testNetlinkMessage() throws Exception { + public void testSendIpv4NfGenMsg() throws Exception { FileDescriptor writeSocket = new FileDescriptor(); FileDescriptor readSocket = new FileDescriptor(); try { @@ -229,17 +231,25 @@ public final class OffloadHardwareInterfaceTest { } when(mNativeHandle.getFileDescriptor()).thenReturn(writeSocket); - mOffloadHw.sendNetlinkMessage(mNativeHandle, TEST_TYPE, TEST_FLAGS); + mOffloadHw.sendIpv4NfGenMsg(mNativeHandle, TEST_TYPE, TEST_FLAGS); + + ByteBuffer buffer = ByteBuffer.allocate(9823); // Arbitrary value > expectedLen. + buffer.order(ByteOrder.nativeOrder()); - ByteBuffer buffer = ByteBuffer.allocate(StructNlMsgHdr.STRUCT_SIZE); int read = Os.read(readSocket, buffer); + final int expectedLen = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; + assertEquals(expectedLen, read); buffer.flip(); - assertEquals(StructNlMsgHdr.STRUCT_SIZE, buffer.getInt()); + assertEquals(expectedLen, buffer.getInt()); assertEquals(TEST_TYPE, buffer.getShort()); assertEquals(TEST_FLAGS, buffer.getShort()); - assertEquals(1 /* seq */, buffer.getInt()); + assertEquals(0 /* seq */, buffer.getInt()); assertEquals(0 /* pid */, buffer.getInt()); + assertEquals(AF_INET, buffer.get()); // nfgen_family + assertEquals(0 /* error */, buffer.get()); // version + assertEquals(0 /* error */, buffer.getShort()); // res_id + assertEquals(expectedLen, buffer.position()); } private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) { diff --git a/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml index 6842dde36264..d719a97e28f2 100644 --- a/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml +++ b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml @@ -21,7 +21,6 @@ android:versionName="1.0"> <overlay android:targetPackage="android" - android:targetName="IconShapeCustomization" android:category="android.theme.customization.adaptive_icon_shape" android:priority="1"/> diff --git a/packages/overlays/IconShapePebbleOverlay/res/values/config.xml b/packages/overlays/IconShapePebbleOverlay/res/values/config.xml index 2465fe015538..e7eeb30501b5 100644 --- a/packages/overlays/IconShapePebbleOverlay/res/values/config.xml +++ b/packages/overlays/IconShapePebbleOverlay/res/values/config.xml @@ -18,7 +18,7 @@ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- Specifies the path that is used by AdaptiveIconDrawable class to crop launcher icons. --> - <string name="config_icon_mask" translatable="false">"MM55,0 C25,0 0,25 0,50 0,78 28,100 55,100 85,100 100,85 100,58 100,30 86,0 55,0 Z"</string> + <string name="config_icon_mask" translatable="false">"M55,0 C25,0 0,25 0,50 0,78 28,100 55,100 85,100 100,85 100,58 100,30 86,0 55,0 Z"</string> <!-- Flag indicating whether round icons should be parsed from the application manifest. --> <bool name="config_useRoundIcon">false</bool> <!-- Corner radius of system dialogs --> diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 34d2b73ac2db..15bd4dc66ccc 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -252,6 +252,10 @@ message SystemMessage { // Package: android NOTE_ID_WIFI_SIM_REQUIRED = 60; + // TODO: remove this notification after feature development is done + // Inform the user a foreground service is restricted from BG-launch. + NOTE_FOREGROUND_SERVICE_BG_LAUNCH = 61; + // Display the Android Debug Protocol status // Package: android NOTE_ADB_WIFI_ACTIVE = 62; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 80e9703e0e62..ae33f0c3da9c 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -119,8 +119,8 @@ import com.android.internal.util.IntPair; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.accessibility.magnification.FullScreenMagnificationController; +import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.MagnificationGestureHandler; -import com.android.server.accessibility.magnification.MagnificationTransitionController; import com.android.server.accessibility.magnification.WindowMagnificationManager; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -219,16 +219,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // Lazily initialized - access through getSystemActionPerfomer() private SystemActionPerformer mSystemActionPerformer; - private FullScreenMagnificationController mFullScreenMagnificationController; - private InteractionBridge mInteractionBridge; private AlertDialog mEnableTouchExplorationDialog; private AccessibilityInputFilter mInputFilter; - private WindowMagnificationManager mWindowMagnificationMgr; - private boolean mHasInputFilter; private KeyEventDispatcher mKeyEventDispatcher; @@ -259,7 +255,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private Point mTempPoint = new Point(); private boolean mIsAccessibilityButtonShown; - private MagnificationTransitionController mMagnificationTransitionController; + private MagnificationController mMagnificationController; private AccessibilityUserState getCurrentUserStateLocked() { return getUserStateLocked(mCurrentUserId); @@ -292,7 +288,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub SystemActionPerformer systemActionPerformer, AccessibilityWindowManager a11yWindowManager, AccessibilityDisplayListener a11yDisplayListener, - WindowMagnificationManager windowMagnificationMgr) { + MagnificationController magnificationController) { mContext = context; mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); @@ -303,8 +299,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mSystemActionPerformer = systemActionPerformer; mA11yWindowManager = a11yWindowManager; mA11yDisplayListener = a11yDisplayListener; - mWindowMagnificationMgr = windowMagnificationMgr; - mMagnificationTransitionController = new MagnificationTransitionController(this, mLock); + mMagnificationController = magnificationController; init(); } @@ -324,7 +319,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler, mWindowManagerService, this, mSecurityPolicy, this); mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); - mMagnificationTransitionController = new MagnificationTransitionController(this, mLock); + mMagnificationController = new MagnificationController(this, mLock, mContext); init(); } @@ -1195,9 +1190,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // The user changed. mCurrentUserId = userId; - if (mWindowMagnificationMgr != null) { - mWindowMagnificationMgr.setUserId(mCurrentUserId); - } + mMagnificationController.updateUserIdIfNeeded(mCurrentUserId); AccessibilityUserState userState = getCurrentUserStateLocked(); readConfigurationForUserStateLocked(userState); @@ -1554,7 +1547,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (fallBackMagnificationModeSettingsLocked(userState)) { return; } - mMagnificationTransitionController.transitionMagnificationModeLocked( + mMagnificationController.transitionMagnificationModeLocked( Display.DEFAULT_DISPLAY, userState.getMagnificationModeLocked(), this::onMagnificationTransitionEndedLocked); } @@ -2310,13 +2303,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return; } - if (mFullScreenMagnificationController != null) { - mFullScreenMagnificationController.setUserId(userState.mUserId); - } - if (mUiAutomationManager.suppressingAccessibilityServicesLocked() - && mFullScreenMagnificationController != null) { - mFullScreenMagnificationController.unregisterAll(); + && mMagnificationController.isFullScreenMagnificationControllerInitialized()) { + getFullScreenMagnificationController().unregisterAll(); return; } @@ -2339,8 +2328,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final int displayId = display.getDisplayId(); if (userHasListeningMagnificationServicesLocked(userState, displayId)) { getFullScreenMagnificationController().register(displayId); - } else if (mFullScreenMagnificationController != null) { - mFullScreenMagnificationController.unregister(displayId); + } else if (mMagnificationController.isFullScreenMagnificationControllerInitialized()) { + getFullScreenMagnificationController().unregister(displayId); } } } @@ -2990,10 +2979,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ public WindowMagnificationManager getWindowMagnificationMgr() { synchronized (mLock) { - if (mWindowMagnificationMgr == null) { - mWindowMagnificationMgr = new WindowMagnificationManager(mContext, mCurrentUserId); - } - return mWindowMagnificationMgr; + return mMagnificationController.getWindowMagnificationMgr(); } } @@ -3060,12 +3046,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public FullScreenMagnificationController getFullScreenMagnificationController() { synchronized (mLock) { - if (mFullScreenMagnificationController == null) { - mFullScreenMagnificationController = new FullScreenMagnificationController(mContext, - this, mLock); - mFullScreenMagnificationController.setUserId(mCurrentUserId); - } - return mFullScreenMagnificationController; + return mMagnificationController.getFullScreenMagnificationController(); } } @@ -3307,9 +3288,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } } - if (mFullScreenMagnificationController != null) { - mFullScreenMagnificationController.onDisplayRemoved(displayId); - } + mMagnificationController.onDisplayRemoved(displayId); mA11yWindowManager.stopTrackingWindows(displayId); } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationTransitionController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index af4b34f9613a..0dc5267c47f6 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationTransitionController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -21,6 +21,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; @@ -29,21 +30,26 @@ import android.util.Slog; import android.util.SparseArray; import android.view.accessibility.MagnificationAnimationCallback; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.accessibility.AccessibilityManagerService; /** - * Handles magnification mode transition. + * Handles all magnification controllers initialization, generic interactions + * and magnification mode transition. */ -public class MagnificationTransitionController { +public class MagnificationController { private static final boolean DEBUG = false; private static final String TAG = "MagnificationController"; private final AccessibilityManagerService mAms; private final PointF mTempPoint = new PointF(); private final Object mLock; + private final Context mContext; private final SparseArray<DisableMagnificationCallback> mMagnificationEndRunnableSparseArray = new SparseArray(); + private FullScreenMagnificationController mFullScreenMagnificationController; + private WindowMagnificationManager mWindowMagnificationMgr; /** * A callback to inform the magnification transition result. @@ -56,9 +62,20 @@ public class MagnificationTransitionController { void onResult(boolean success); } - public MagnificationTransitionController(AccessibilityManagerService ams, Object lock) { + public MagnificationController(AccessibilityManagerService ams, Object lock, + Context context) { mAms = ams; mLock = lock; + mContext = context; + } + + @VisibleForTesting + public MagnificationController(AccessibilityManagerService ams, Object lock, + Context context, FullScreenMagnificationController fullScreenMagnificationController, + WindowMagnificationManager windowMagnificationManager) { + this(ams, lock, context); + mFullScreenMagnificationController = fullScreenMagnificationController; + mWindowMagnificationMgr = windowMagnificationManager; } /** @@ -97,7 +114,7 @@ public class MagnificationTransitionController { } final FullScreenMagnificationController screenMagnificationController = getFullScreenMagnificationController(); - final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationManager(); + final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr(); final float scale = windowMagnificationMgr.getPersistedScale(); final DisableMagnificationCallback animationEndCallback = new DisableMagnificationCallback(transitionCallBack, displayId, targetMode, @@ -111,6 +128,39 @@ public class MagnificationTransitionController { setDisableMagnificationCallbackLocked(displayId, animationEndCallback); } + /** + * Updates the active user ID of {@link FullScreenMagnificationController} and {@link + * WindowMagnificationManager}. + * + * @param userId the currently active user ID + */ + public void updateUserIdIfNeeded(int userId) { + synchronized (mLock) { + if (mFullScreenMagnificationController != null) { + mFullScreenMagnificationController.setUserId(userId); + } + if (mWindowMagnificationMgr != null) { + mWindowMagnificationMgr.setUserId(userId); + } + } + } + + /** + * Removes the magnification instance with given id. + * + * @param displayId The logical display id. + */ + public void onDisplayRemoved(int displayId) { + synchronized (mLock) { + if (mFullScreenMagnificationController != null) { + mFullScreenMagnificationController.onDisplayRemoved(displayId); + } + if (mWindowMagnificationMgr != null) { + mWindowMagnificationMgr.onDisplayRemoved(displayId); + } + } + } + private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked( int displayId) { return mMagnificationEndRunnableSparseArray.get(displayId); @@ -125,31 +175,63 @@ public class MagnificationTransitionController { } } - private FullScreenMagnificationController getFullScreenMagnificationController() { - return mAms.getFullScreenMagnificationController(); + /** + * Getter of {@link FullScreenMagnificationController}. + * + * @return {@link FullScreenMagnificationController}. + */ + public FullScreenMagnificationController getFullScreenMagnificationController() { + synchronized (mLock) { + if (mFullScreenMagnificationController == null) { + mFullScreenMagnificationController = new FullScreenMagnificationController(mContext, + mAms, mLock); + mFullScreenMagnificationController.setUserId(mAms.getCurrentUserIdLocked()); + } + } + return mFullScreenMagnificationController; + } + + /** + * Is {@link #mFullScreenMagnificationController} is initialized. + * @return {code true} if {@link #mFullScreenMagnificationController} is initialized. + */ + public boolean isFullScreenMagnificationControllerInitialized() { + synchronized (mLock) { + return mFullScreenMagnificationController != null; + } } - private WindowMagnificationManager getWindowMagnificationManager() { - return mAms.getWindowMagnificationMgr(); + /** + * Getter of {@link WindowMagnificationManager}. + * + * @return {@link WindowMagnificationManager}. + */ + public WindowMagnificationManager getWindowMagnificationMgr() { + synchronized (mLock) { + if (mWindowMagnificationMgr == null) { + mWindowMagnificationMgr = new WindowMagnificationManager(mContext, + mAms.getCurrentUserIdLocked()); + } + return mWindowMagnificationMgr; + } } private @Nullable PointF getCurrentMagnificationBoundsCenterLocked(int displayId, int targetMode) { if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { - final WindowMagnificationManager magnificationManager = getWindowMagnificationManager(); - if (!magnificationManager.isWindowMagnifierEnabled(displayId)) { + if (mWindowMagnificationMgr == null + || !mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId)) { return null; } - mTempPoint.set(magnificationManager.getCenterX(displayId), - magnificationManager.getCenterY(displayId)); + mTempPoint.set(mWindowMagnificationMgr.getCenterX(displayId), + mWindowMagnificationMgr.getCenterY(displayId)); } else { - final FullScreenMagnificationController screenMagnificationController = - getFullScreenMagnificationController(); - if (!screenMagnificationController.isMagnifying(displayId)) { + if (mFullScreenMagnificationController == null + || !mFullScreenMagnificationController.isMagnifying(displayId)) { return null; } - mTempPoint.set(screenMagnificationController.getCenterX(displayId), - screenMagnificationController.getCenterY(displayId)); + mTempPoint.set(mFullScreenMagnificationController.getCenterX(displayId), + mFullScreenMagnificationController.getCenterY(displayId)); } return mTempPoint; } @@ -228,7 +310,7 @@ public class MagnificationTransitionController { mCurrentCenter.y, true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); } else { - getWindowMagnificationManager().enableWindowMagnification(mDisplayId, + getWindowMagnificationMgr().enableWindowMagnification(mDisplayId, mCurrentScale, mCurrentCenter.x, mCurrentCenter.y); } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index c8e485f503ec..ed3085f97ded 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -452,6 +452,15 @@ public class WindowMagnificationManager implements return magnifier; } + /** + * Removes the window magnifier with given id. + * + * @param displayId The logical display id. + */ + void onDisplayRemoved(int displayId) { + disableWindowMagnification(displayId, true); + } + private class ConnectionCallback extends IWindowMagnificationConnectionCallback.Stub implements IBinder.DeathRecipient { private boolean mExpiredDeathRecipient = false; diff --git a/services/backup/java/com/android/server/backup/DataChangedJournal.java b/services/backup/java/com/android/server/backup/DataChangedJournal.java index 0e7fc93df7cc..4eb1d9a6a7c5 100644 --- a/services/backup/java/com/android/server/backup/DataChangedJournal.java +++ b/services/backup/java/com/android/server/backup/DataChangedJournal.java @@ -40,7 +40,7 @@ import java.util.function.Consumer; * <p>This information is persisted to the filesystem so that it is not lost in the event of a * reboot. */ -public final class DataChangedJournal { +public class DataChangedJournal { private static final String TAG = "DataChangedJournal"; private static final String FILE_NAME_PREFIX = "journal"; diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 8a1baf25481b..da5d1c2aa61e 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -136,8 +136,6 @@ import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.netlink.InetDiagMessage; import android.net.shared.PrivateDnsConfig; -import android.net.util.LinkPropertiesUtils.CompareOrUpdateResult; -import android.net.util.LinkPropertiesUtils.CompareResult; import android.net.util.MultinetworkPolicyTracker; import android.net.util.NetdService; import android.os.Binder; @@ -195,6 +193,8 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.LocationPermissionChecker; import com.android.internal.util.MessageUtils; import com.android.internal.util.XmlUtils; +import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult; +import com.android.net.module.util.LinkPropertiesUtils.CompareResult; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.AutodestructReference; import com.android.server.connectivity.DataConnectionStats; diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java index 4a1820a8e538..d90750548114 100644 --- a/services/core/java/com/android/server/NsdService.java +++ b/services/core/java/com/android/server/NsdService.java @@ -25,7 +25,6 @@ import android.net.Uri; import android.net.nsd.INsdManager; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; -import android.net.util.nsd.DnsSdTxtRecord; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; @@ -42,6 +41,7 @@ import com.android.internal.util.AsyncChannel; import com.android.internal.util.DumpUtils; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.android.net.module.util.DnsSdTxtRecord; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index 34e63705d781..b87468419430 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -17,13 +17,13 @@ package com.android.server; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.UserInfo; import android.os.Environment; import android.os.SystemClock; import android.os.Trace; -import android.os.UserHandle; import android.os.UserManagerInternal; import android.util.ArrayMap; import android.util.Slog; @@ -84,6 +84,13 @@ public final class SystemServiceManager { @GuardedBy("mTargetUsers") private final SparseArray<TargetUser> mTargetUsers = new SparseArray<>(); + /** + * Reference to the current user, it's used to set the {@link TargetUser} on + * {@link #switchUser(int, int)} as the previous user might have been removed already. + */ + @GuardedBy("mTargetUsers") + private @Nullable TargetUser mCurrentUser; + SystemServiceManager(Context context) { mContext = context; } @@ -259,18 +266,22 @@ public final class SystemServiceManager { return targetUser; } + private @NonNull TargetUser newTargetUser(@UserIdInt int userId) { + final UserInfo userInfo = mUserManagerInternal.getUserInfo(userId); + Preconditions.checkState(userInfo != null, "No UserInfo for " + userId); + return new TargetUser(userInfo); + } + /** * Starts the given user. */ public void startUser(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) { - // Create cached TargetUser - final UserInfo userInfo = mUserManagerInternal.getUserInfo(userId); - Preconditions.checkState(userInfo != null, "No UserInfo for " + userId); + final TargetUser targetUser = newTargetUser(userId); synchronized (mTargetUsers) { - mTargetUsers.put(userId, new TargetUser(userInfo)); + mTargetUsers.put(userId, targetUser); } - onUser(t, START, userId); + onUser(t, START, /* prevUser= */ null, targetUser); } /** @@ -291,7 +302,26 @@ public final class SystemServiceManager { * Switches to the given user. */ public void switchUser(@UserIdInt int from, @UserIdInt int to) { - onUser(TimingsTraceAndSlog.newAsyncLog(), SWITCH, to, from); + final TargetUser curUser, prevUser; + synchronized (mTargetUsers) { + if (mCurrentUser == null) { + if (DEBUG) { + Slog.d(TAG, "First user switch: from " + from + " to " + to); + } + prevUser = newTargetUser(from); + } else { + if (from != mCurrentUser.getUserIdentifier()) { + Slog.wtf(TAG, "switchUser(" + from + "," + to + "): mCurrentUser is " + + mCurrentUser + ", it should be " + from); + } + prevUser = mCurrentUser; + } + curUser = mCurrentUser = getTargetUser(to); + if (DEBUG) { + Slog.d(TAG, "Set mCurrentUser to " + mCurrentUser); + } + } + onUser(TimingsTraceAndSlog.newAsyncLog(), SWITCH, prevUser, curUser); } /** @@ -314,21 +344,16 @@ public final class SystemServiceManager { } private void onUser(@NonNull String onWhat, @UserIdInt int userId) { - onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, userId); + onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, /* prevUser= */ null, + getTargetUser(userId)); } private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat, - @UserIdInt int userId) { - onUser(t, onWhat, userId, UserHandle.USER_NULL); - } - - private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat, - @UserIdInt int curUserId, @UserIdInt int prevUserId) { + @Nullable TargetUser prevUser, @NonNull TargetUser curUser) { + final int curUserId = curUser.getUserIdentifier(); t.traceBegin("ssm." + onWhat + "User-" + curUserId); - Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId); - final TargetUser curUser = getTargetUser(curUserId); - final TargetUser prevUser = prevUserId == UserHandle.USER_NULL ? null - : getTargetUser(prevUserId); + Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId + + (prevUser != null ? " (from " + prevUser + ")" : "")); final int serviceLen = mServices.size(); for (int i = 0; i < serviceLen; i++) { final SystemService service = mServices.get(i); @@ -466,14 +491,16 @@ public final class SystemServiceManager { .append(service.getClass().getSimpleName()) .append("\n"); } - - builder.append("Target users: "); - final int targetUsersSize = mTargetUsers.size(); - for (int i = 0; i < targetUsersSize; i++) { - mTargetUsers.valueAt(i).dump(builder); - if (i != targetUsersSize - 1) builder.append(','); + synchronized (mTargetUsers) { + builder.append("Current user: ").append(mCurrentUser).append('\n'); + builder.append("Target users: "); + final int targetUsersSize = mTargetUsers.size(); + for (int i = 0; i < targetUsersSize; i++) { + mTargetUsers.valueAt(i).dump(builder); + if (i != targetUsersSize - 1) builder.append(','); + } + builder.append('\n'); } - builder.append('\n'); Slog.e(TAG, builder.toString()); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 91a3fb003df2..b2e021fb74d1 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -25,6 +25,7 @@ import static android.os.Process.SHELL_UID; import static android.os.Process.SYSTEM_UID; import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY; +import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICE_BG_LAUNCH; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; @@ -119,6 +120,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -228,6 +230,10 @@ public final class ActiveServices { // white listed packageName. ArraySet<String> mWhiteListAllowWhileInUsePermissionInFgs = new ArraySet<>(); + // TODO: remove this after feature development is done + private static final SimpleDateFormat DATE_FORMATTER = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + final Runnable mLastAnrDumpClearer = new Runnable() { @Override public void run() { synchronized (mAm) { @@ -553,8 +559,9 @@ public final class ActiveServices { if (r.mAllowStartForeground == FGS_FEATURE_DENIED && mAm.mConstants.mFlagFgsStartRestrictionEnabled) { Slog.w(TAG, "startForegroundService() not allowed due to " - + " mAllowStartForeground false: service " + + "mAllowStartForeground false: service " + r.shortInstanceName); + showFgsBgRestrictedNotificationLocked(r); forcedStandby = true; } } @@ -1459,6 +1466,7 @@ public final class ActiveServices { "Service.startForeground() not allowed due to " + "mAllowStartForeground false: service " + r.shortInstanceName); + showFgsBgRestrictedNotificationLocked(r); updateServiceForegroundLocked(r.app, true); ignoreForeground = true; } @@ -5056,4 +5064,27 @@ public final class ActiveServices { && code != FGS_FEATURE_ALLOWED_BY_UID_VISIBLE; } + // TODO: remove this notification after feature development is done + private void showFgsBgRestrictedNotificationLocked(ServiceRecord r) { + final Context context = mAm.mContext; + final String title = "Foreground Service BG-Launch Restricted"; + final String content = "App restricted: " + r.mRecentCallingPackage; + final long now = System.currentTimeMillis(); + final String bigText = DATE_FORMATTER.format(now) + " " + r.mInfoAllowStartForeground; + final String groupKey = "com.android.fgs-bg-restricted"; + final Notification.Builder n = + new Notification.Builder(context, + SystemNotificationChannels.ALERTS) + .setGroup(groupKey) + .setSmallIcon(R.drawable.stat_sys_vitals) + .setWhen(0) + .setColor(context.getColor( + com.android.internal.R.color.system_notification_accent_color)) + .setTicker(title) + .setContentTitle(title) + .setContentText(content) + .setStyle(new Notification.BigTextStyle().bigText(bigText)); + context.getSystemService(NotificationManager.class).notifyAsUser(Long.toString(now), + NOTE_FOREGROUND_SERVICE_BG_LAUNCH, n.build(), UserHandle.ALL); + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 4f056df4dff9..ffdcd156122e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -17334,4 +17334,18 @@ public class ActivityManagerService extends IActivityManager.Stub throw new SecurityException("Caller uid " + callerUid + " cannot set freezer state "); } } + + /** + * Holds the AM lock for the specified amount of milliseconds. + * Intended for use by the tests that need to imitate lock contention. + * Requires permission identity of the shell UID. + */ + @Override + public void holdLock(int durationMs) { + enforceCallingPermission(Manifest.permission.INJECT_EVENTS, "holdLock"); + + synchronized (this) { + SystemClock.sleep(durationMs); + } + } } diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 757b4e83e120..dbf91ee64333 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -86,7 +86,7 @@ final class CoreSettingsObserver extends ContentObserver { sGlobalSettingToTypeMap.put( Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE, String.class); sGlobalSettingToTypeMap.put( - Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE, String.class); + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE, int.class); sGlobalSettingToTypeMap.put( Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, String.class); sGlobalSettingToTypeMap.put( @@ -94,6 +94,8 @@ final class CoreSettingsObserver extends ContentObserver { sGlobalSettingToTypeMap.put( Settings.Global.GLOBAL_SETTINGS_ANGLE_ALLOWLIST, String.class); sGlobalSettingToTypeMap.put( + Settings.Global.ANGLE_EGL_FEATURES, String.class); + sGlobalSettingToTypeMap.put( Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX, String.class); sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class); sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 1bf62a0c7b7e..58ac2dc869ce 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -733,10 +733,6 @@ public final class OomAdjuster { uidRec.reset(); } - if (mService.mAtmInternal != null) { - mService.mAtmInternal.rankTaskLayersIfNeeded(); - } - mAdjSeq++; if (fullUpdate) { mNewNumServiceProcs = 0; diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 2dced8d704bb..ebc5b59363df 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1909,8 +1909,8 @@ class ProcessRecord implements WindowProcessListener { } callback.initialize(this, adj, foregroundActivities, procState, schedGroup, appUid, logUid, processCurTop); - final int minLayer = getWindowProcessController().computeOomAdjFromActivities( - ProcessList.VISIBLE_APP_LAYER_MAX, callback); + final int minLayer = Math.min(ProcessList.VISIBLE_APP_LAYER_MAX, + getWindowProcessController().computeOomAdjFromActivities(callback)); mCachedAdj = callback.adj; mCachedForegroundActivities = callback.foregroundActivities; diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index eb60573e6f17..6c088262a7bf 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -712,8 +712,6 @@ class UserController implements Handler.Callback { Slog.i(TAG, "Stopping pre-created user " + userInfo.toFullString()); // Pre-created user was started right after creation so services could properly // intialize it; it should be stopped right away as it's not really a "real" user. - // TODO(b/143092698): in the long-term, it might be better to add a onCreateUser() - // callback on SystemService instead. stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false, /* stopUserCallback= */ null, /* keyEvictedCallback= */ null); return; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 668713f0fee7..c1777b847d9b 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -584,29 +584,6 @@ public class AppOpsService extends IAppOpsService.Stub { // process is not in foreground. return MODE_IGNORED; } - } else if (mode == MODE_ALLOWED) { - switch (op) { - case OP_CAMERA: - if (mActivityManagerInternal != null - && mActivityManagerInternal.isPendingTopUid(uid)) { - return MODE_ALLOWED; - } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) { - return MODE_ALLOWED; - } else { - return MODE_IGNORED; - } - case OP_RECORD_AUDIO: - if (mActivityManagerInternal != null - && mActivityManagerInternal.isPendingTopUid(uid)) { - return MODE_ALLOWED; - } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) { - return MODE_ALLOWED; - } else { - return MODE_IGNORED; - } - default: - return MODE_ALLOWED; - } } return mode; } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index f1561cab8fb4..7ad0f21c2f69 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -297,6 +297,7 @@ public class AudioService extends IAudioService.Stub // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) private static final int MSG_DISABLE_AUDIO_FOR_UID = 100; + private static final int MSG_INIT_STREAMS_VOLUMES = 101; // end of messages handled under wakelock // retry delay in case of failure to indicate system ready to AudioFlinger @@ -822,7 +823,34 @@ public class AudioService extends IAudioService.Stub updateStreamVolumeAlias(false /*updateVolumes*/, TAG); readPersistedSettings(); readUserRestrictions(); - mSettingsObserver = new SettingsObserver(); + + mPlaybackMonitor = + new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]); + mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor); + + readAndSetLowRamDevice(); + + mIsCallScreeningModeSupported = AudioSystem.isCallScreeningModeSupported(); + + if (mSystemServer.isPrivileged()) { + LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal()); + + mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener); + + mRecordMonitor.initMonitor(); + } + + mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false); + + // done with service initialization, continue additional work in our Handler thread + queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES, + 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); + } + + /** + * Called by handling of MSG_INIT_STREAMS_VOLUMES + */ + private void onInitStreamsAndVolumes() { createStreamStates(); // must be called after createStreamStates() as it uses MUSIC volume as default if no @@ -833,20 +861,42 @@ public class AudioService extends IAudioService.Stub // relies on audio policy having correct ranges for volume indexes. mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex(); - mPlaybackMonitor = - new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]); - - mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor); - - readAndSetLowRamDevice(); - - mIsCallScreeningModeSupported = AudioSystem.isCallScreeningModeSupported(); - // Call setRingerModeInt() to apply correct mute // state on streams affected by ringer mode. mRingerAndZenModeMutedStreams = 0; setRingerModeInt(getRingerModeInternal(), false); + final float[] preScale = new float[3]; + preScale[0] = mContext.getResources().getFraction( + com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index1, + 1, 1); + preScale[1] = mContext.getResources().getFraction( + com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index2, + 1, 1); + preScale[2] = mContext.getResources().getFraction( + com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index3, + 1, 1); + for (int i = 0; i < preScale.length; i++) { + if (0.0f <= preScale[i] && preScale[i] <= 1.0f) { + mPrescaleAbsoluteVolume[i] = preScale[i]; + } + } + + initExternalEventReceivers(); + + // check on volume initialization + checkVolumeRangeInitialization("AudioService()"); + } + + /** + * Initialize intent receives and settings observers for this service. + * Must be called after createStreamStates() as the handling of some events + * may affect or need volumes, e.g. BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED + * (for intent receiver), or Settings.Global.ZEN_MODE (for settings observer) + */ + private void initExternalEventReceivers() { + mSettingsObserver = new SettingsObserver(); + // Register for device connection intent broadcasts. IntentFilter intentFilter = new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); @@ -862,7 +912,6 @@ public class AudioService extends IAudioService.Stub intentFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED); intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false); if (mMonitorRotation) { RotationHelper.init(mContext, mAudioHandler); } @@ -870,34 +919,8 @@ public class AudioService extends IAudioService.Stub intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); intentFilter.addAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); - context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null); - - if (mSystemServer.isPrivileged()) { - LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal()); - - mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener); + mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null); - mRecordMonitor.initMonitor(); - } - - final float[] preScale = new float[3]; - preScale[0] = mContext.getResources().getFraction( - com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index1, - 1, 1); - preScale[1] = mContext.getResources().getFraction( - com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index2, - 1, 1); - preScale[2] = mContext.getResources().getFraction( - com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index3, - 1, 1); - for (int i = 0; i < preScale.length; i++) { - if (0.0f <= preScale[i] && preScale[i] <= 1.0f) { - mPrescaleAbsoluteVolume[i] = preScale[i]; - } - } - - // check on volume initialization - checkVolumeRangeInitialization("AudioService()"); } public void systemReady() { @@ -2593,7 +2616,7 @@ public class AudioService extends IAudioService.Stub // StreamVolumeCommand contains the information needed to defer the process of // setStreamVolume() in case the user has to acknowledge the safe volume warning message. - class StreamVolumeCommand { + static class StreamVolumeCommand { public final int mStreamType; public final int mIndex; public final int mFlags; @@ -2612,7 +2635,7 @@ public class AudioService extends IAudioService.Stub .append(mIndex).append(",flags=").append(mFlags).append(",device=") .append(mDevice).append('}').toString(); } - }; + } private int getNewRingerMode(int stream, int index, int flags) { // setRingerMode does nothing if the device is single volume,so the value would be unchanged @@ -3321,7 +3344,7 @@ public class AudioService extends IAudioService.Stub } private int mRmtSbmxFullVolRefCount = 0; - private ArrayList<RmtSbmxFullVolDeathHandler> mRmtSbmxFullVolDeathHandlers = + private final ArrayList<RmtSbmxFullVolDeathHandler> mRmtSbmxFullVolDeathHandlers = new ArrayList<RmtSbmxFullVolDeathHandler>(); public void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb) { @@ -5882,7 +5905,6 @@ public class AudioService extends IAudioService.Stub private final Intent mStreamDevicesChanged; private VolumeStreamState(String settingName, int streamType) { - mVolumeIndexSettingName = settingName; mStreamType = streamType; @@ -6658,6 +6680,11 @@ public class AudioService extends IAudioService.Stub mAudioEventWakeLock.release(); break; + case MSG_INIT_STREAMS_VOLUMES: + onInitStreamsAndVolumes(); + mAudioEventWakeLock.release(); + break; + case MSG_CHECK_MUSIC_ACTIVE: onCheckMusicActive((String) msg.obj); break; @@ -9216,7 +9243,7 @@ public class AudioService extends IAudioService.Stub } } - private HashMap<IBinder, AsdProxy> mAudioServerStateListeners = + private final HashMap<IBinder, AsdProxy> mAudioServerStateListeners = new HashMap<IBinder, AsdProxy>(); private void checkMonitorAudioServerStatePermission() { diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index c300169d0b39..88804e28480b 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -49,8 +49,10 @@ import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorProperties; +import android.hardware.biometrics.SensorPropertiesInternal; import android.os.Binder; import android.os.Build; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -448,7 +450,7 @@ public class Utils { /** * Converts from {@link BiometricManager.Authenticators} biometric strength to the internal - * {@link SensorProperties} strength. + * {@link SensorPropertiesInternal} strength. */ public static @SensorProperties.Strength int authenticatorStrengthToPropertyStrength( @BiometricManager.Authenticators.Types int strength) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java index c17bc917071c..bff0c3c686ef 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java @@ -31,7 +31,7 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; import android.hardware.face.Face; -import android.hardware.face.FaceSensorProperties; +import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; import android.os.Build; @@ -87,7 +87,7 @@ class Face10 implements IHwBinder.DeathRecipient { static final String NOTIFICATION_TAG = "FaceService"; static final int NOTIFICATION_ID = 1; - @NonNull private final FaceSensorProperties mFaceSensorProperties; + @NonNull private final FaceSensorPropertiesInternal mFaceSensorProperties; @NonNull private final Context mContext; @NonNull private final BiometricScheduler mScheduler; @NonNull private final Handler mHandler; @@ -285,8 +285,8 @@ class Face10 implements IHwBinder.DeathRecipient { .getBoolean(R.bool.config_faceAuthSupportsSelfIllumination); final int maxTemplatesAllowed = context.getResources() .getInteger(R.integer.config_faceMaxTemplatesPerUser); - mFaceSensorProperties = new FaceSensorProperties(sensorId, false /* supportsFaceDetect */, - supportsSelfIllumination, maxTemplatesAllowed); + mFaceSensorProperties = new FaceSensorPropertiesInternal(sensorId, strength, + maxTemplatesAllowed, false /* supportsFaceDetect */, supportsSelfIllumination); mContext = context; mSensorId = sensorId; mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */); @@ -672,7 +672,8 @@ class Face10 implements IHwBinder.DeathRecipient { return daemon != null; } - @NonNull FaceSensorProperties getFaceSensorProperties() { + @NonNull + FaceSensorPropertiesInternal getFaceSensorProperties() { return mFaceSensorProperties; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 82dc0d04d8a7..83f10c8e658b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -26,7 +26,7 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.face.Face; -import android.hardware.face.FaceSensorProperties; +import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.IFaceService; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; @@ -67,9 +67,11 @@ public class FaceService extends SystemService { */ private final class FaceServiceWrapper extends IFaceService.Stub { @Override // Binder call - public List<FaceSensorProperties> getSensorProperties(String opPackageName) { + public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal( + String opPackageName) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - final List<FaceSensorProperties> properties = new ArrayList<>(); + + final List<FaceSensorPropertiesInternal> properties = new ArrayList<>(); if (mFace10 != null) { properties.add(mFace10.getFaceSensorProperties()); 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 2903b9970033..d353994b7f9a 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 @@ -33,7 +33,7 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.fingerprint.Fingerprint; -import android.hardware.fingerprint.FingerprintSensorProperties; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintClientActiveCallback; import android.hardware.fingerprint.IFingerprintService; import android.hardware.fingerprint.IFingerprintServiceReceiver; @@ -46,6 +46,7 @@ import android.os.Process; import android.os.UserHandle; import android.provider.Settings; import android.util.EventLog; +import android.util.Pair; import android.util.Slog; import android.view.Surface; @@ -79,20 +80,19 @@ public class FingerprintService extends SystemService { private final LockoutResetDispatcher mLockoutResetDispatcher; private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; private final LockPatternUtils mLockPatternUtils; - private Fingerprint21 mFingerprint21; + @NonNull private List<ServiceProvider> mServiceProviders; /** * Receives the incoming binder calls from FingerprintManager. */ private final class FingerprintServiceWrapper extends IFingerprintService.Stub { @Override // Binder call - public List<FingerprintSensorProperties> getSensorProperties(String opPackageName) { + public List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal( + String opPackageName) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - final List<FingerprintSensorProperties> properties = new ArrayList<>(); - if (mFingerprint21 != null) { - properties.add(mFingerprint21.getFingerprintSensorProperties()); - } + final List<FingerprintSensorPropertiesInternal> properties = + FingerprintService.this.getSensorProperties(); Slog.d(TAG, "Retrieved sensor properties for: " + opPackageName + ", sensors: " + properties.size()); @@ -104,18 +104,26 @@ public class FingerprintService extends SystemService { IFingerprintServiceReceiver receiver, String opPackageName) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); - if (sensorId == mFingerprint21.getFingerprintSensorProperties().sensorId) { - mFingerprint21.scheduleGenerateChallenge(token, receiver, opPackageName); + final ServiceProvider provider = getProviderForSensor(sensorId); + if (provider == null) { + Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId); return; } - Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId); + provider.scheduleGenerateChallenge(sensorId, token, receiver, opPackageName); } @Override // Binder call - public void revokeChallenge(IBinder token, String owner) { + public void revokeChallenge(IBinder token, String opPackageName) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); - mFingerprint21.scheduleRevokeChallenge(token, owner); + + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for revokeChallenge"); + return; + } + + provider.second.scheduleRevokeChallenge(provider.first, token, opPackageName); } @Override // Binder call @@ -123,14 +131,28 @@ public class FingerprintService extends SystemService { final IFingerprintServiceReceiver receiver, final String opPackageName, Surface surface) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); - mFingerprint21.scheduleEnroll(token, hardwareAuthToken, userId, receiver, opPackageName, - surface); + + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for enroll"); + return; + } + + provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, + receiver, opPackageName, surface); } @Override // Binder call public void cancelEnrollment(final IBinder token) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); - mFingerprint21.cancelEnrollment(token); + + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for cancelEnrollment"); + return; + } + + provider.second.cancelEnrollment(provider.first, token); } @Override // Binder call @@ -169,8 +191,15 @@ public class FingerprintService extends SystemService { != PackageManager.PERMISSION_GRANTED; final int statsClient = isKeyguard ? BiometricsProtoEnums.CLIENT_KEYGUARD : BiometricsProtoEnums.CLIENT_FINGERPRINT_MANAGER; - mFingerprint21.scheduleAuthenticate(token, operationId, userId, 0 /* cookie */, - new ClientMonitorCallbackConverter(receiver), opPackageName, + + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for authenticate"); + return; + } + + provider.second.scheduleAuthenticate(provider.first, token, operationId, userId, + 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName, restricted, statsClient, isKeyguard); } @@ -191,7 +220,13 @@ public class FingerprintService extends SystemService { return; } - mFingerprint21.scheduleFingerDetect(token, userId, + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for detectFingerprint"); + return; + } + + provider.second.scheduleFingerDetect(provider.first, token, userId, new ClientMonitorCallbackConverter(receiver), opPackageName, surface, BiometricsProtoEnums.CLIENT_KEYGUARD); } @@ -203,8 +238,14 @@ public class FingerprintService extends SystemService { Surface surface) { Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for prepareForAuthentication"); + return; + } + final boolean restricted = true; // BiometricPrompt is always restricted - mFingerprint21.scheduleAuthenticate(token, operationId, userId, cookie, + provider.second.scheduleAuthenticate(provider.first, token, operationId, userId, cookie, new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, false /* isKeyguard */); } @@ -212,7 +253,14 @@ public class FingerprintService extends SystemService { @Override // Binder call public void startPreparedClient(int cookie) { Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); - mFingerprint21.startPreparedClient(cookie); + + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for startPreparedClient"); + return; + } + + provider.second.startPreparedClient(provider.first, cookie); } @@ -228,7 +276,13 @@ public class FingerprintService extends SystemService { return; } - mFingerprint21.cancelAuthentication(token); + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for cancelAuthentication"); + return; + } + + provider.second.cancelAuthentication(provider.first, token); } @Override // Binder call @@ -242,21 +296,41 @@ public class FingerprintService extends SystemService { // For IBiometricsFingerprint2.1, cancelling fingerprint detect is the same as // cancelling authentication. - mFingerprint21.cancelAuthentication(token); + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for cancelFingerprintDetect"); + return; + } + + provider.second.cancelAuthentication(provider.first, token); } @Override // Binder call public void cancelAuthenticationFromService(final IBinder token, final String opPackageName, int callingUid, int callingPid, int callingUserId) { Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); - mFingerprint21.cancelAuthentication(token); + + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for cancelAuthenticationFromService"); + return; + } + + provider.second.cancelAuthentication(provider.first, token); } @Override // Binder call public void remove(final IBinder token, final int fingerId, final int userId, final IFingerprintServiceReceiver receiver, final String opPackageName) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); - mFingerprint21.scheduleRemove(token, receiver, fingerId, userId, opPackageName); + + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for remove"); + return; + } + provider.second.scheduleRemove(provider.first, token, receiver, fingerId, userId, + opPackageName); } @Override @@ -274,10 +348,15 @@ public class FingerprintService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { - if (args.length > 0 && "--proto".equals(args[0])) { - mFingerprint21.dumpProto(fd); - } else { - mFingerprint21.dumpInternal(pw); + for (ServiceProvider provider : mServiceProviders) { + for (FingerprintSensorPropertiesInternal props : + provider.getSensorProperties()) { + if (args.length > 0 && "--proto".equals(args[0])) { + provider.dumpProto(props.sensorId, fd); + } else { + provider.dumpInternal(props.sensorId, pw); + } + } } } finally { Binder.restoreCallingIdentity(ident); @@ -294,11 +373,12 @@ public class FingerprintService extends SystemService { final long token = Binder.clearCallingIdentity(); try { - if (mFingerprint21 == null) { - Slog.e(TAG, "No HAL, caller: " + opPackageName); + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName); return false; } - return mFingerprint21.isHardwareDetected(); + return provider.second.isHardwareDetected(provider.first); } finally { Binder.restoreCallingIdentity(token); } @@ -311,7 +391,13 @@ public class FingerprintService extends SystemService { return; } - mFingerprint21.rename(fingerId, userId, name); + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for rename"); + return; + } + + provider.second.rename(provider.first, fingerId, userId, name); } @Override // Binder call @@ -325,7 +411,8 @@ public class FingerprintService extends SystemService { if (userId != UserHandle.getCallingUserId()) { Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS); } - return mFingerprint21.getEnrolledFingerprints(userId); + + return FingerprintService.this.getEnrolledFingerprints(userId, opPackageName); } @Override // Binder call @@ -339,19 +426,32 @@ public class FingerprintService extends SystemService { if (userId != UserHandle.getCallingUserId()) { Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS); } - return mFingerprint21.getEnrolledFingerprints(userId).size() > 0; + return !FingerprintService.this.getEnrolledFingerprints(userId, opPackageName) + .isEmpty(); } @Override // Binder call public @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - return mFingerprint21.getLockoutModeForUser(userId); + + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for getLockoutModeForUser"); + return LockoutTracker.LOCKOUT_NONE; + } + return provider.second.getLockoutModeForUser(provider.first, userId); } @Override // Binder call public long getAuthenticatorId(int userId) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - return mFingerprint21.getAuthenticatorId(userId); + + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for getAuthenticatorId"); + return 0; + } + return provider.second.getAuthenticatorId(provider.first, userId); } @Override // Binder call @@ -359,12 +459,13 @@ public class FingerprintService extends SystemService { @Nullable byte [] hardwareAuthToken, String opPackageName) { Utils.checkPermission(getContext(), RESET_FINGERPRINT_LOCKOUT); - if (sensorId == mFingerprint21.getFingerprintSensorProperties().sensorId) { - mFingerprint21.scheduleResetLockout(userId); + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName); return; } - Slog.w(TAG, "No matching sensor for resetLockout, sensorId: " + sensorId); + provider.second.scheduleResetLockout(sensorId, userId, hardwareAuthToken); } @Override @@ -389,35 +490,52 @@ public class FingerprintService extends SystemService { public void initializeConfiguration(int sensorId, int strength) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + final Fingerprint21 fingerprint21; if ((Build.IS_USERDEBUG || Build.IS_ENG) && getContext().getResources().getBoolean(R.bool.allow_test_udfps) && Settings.Secure.getIntForUser(getContext().getContentResolver(), Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */, UserHandle.USER_CURRENT) != 0) { - mFingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), sensorId, + fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), sensorId, strength, mLockoutResetDispatcher, mGestureAvailabilityDispatcher); } else { - mFingerprint21 = Fingerprint21.newInstance(getContext(), sensorId, strength, + fingerprint21 = Fingerprint21.newInstance(getContext(), sensorId, strength, mLockoutResetDispatcher, mGestureAvailabilityDispatcher); } + mServiceProviders.add(fingerprint21); } @Override - public void onFingerDown(int x, int y, float minor, float major) { + public void onFingerDown(int sensorId, int x, int y, float minor, float major) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - mFingerprint21.onFingerDown(x, y, minor, major); + + final ServiceProvider provider = getProviderForSensor(sensorId); + if (provider == null) { + Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId); + return; + } + provider.onFingerDown(sensorId, x, y, minor, major); } @Override - public void onFingerUp() { + public void onFingerUp(int sensorId) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - mFingerprint21.onFingerUp(); + + final ServiceProvider provider = getProviderForSensor(sensorId); + if (provider == null) { + Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId); + return; + } + provider.onFingerUp(sensorId); } @Override - public void setUdfpsOverlayController(IUdfpsOverlayController controller) { + public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); - mFingerprint21.setUdfpsOverlayController(controller); + + for (ServiceProvider provider : mServiceProviders) { + provider.setUdfpsOverlayController(controller); + } } } @@ -427,6 +545,7 @@ public class FingerprintService extends SystemService { mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher(); mLockoutResetDispatcher = new LockoutResetDispatcher(context); mLockPatternUtils = new LockPatternUtils(context); + mServiceProviders = new ArrayList<>(); } @Override @@ -434,6 +553,61 @@ public class FingerprintService extends SystemService { publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper()); } + @Nullable + private ServiceProvider getProviderForSensor(int sensorId) { + for (ServiceProvider provider : mServiceProviders) { + if (provider.containsSensor(sensorId)) { + return provider; + } + } + return null; + } + + /** + * For devices with only a single provider, returns that provider. If no providers, or multiple + * providers exist, returns null. + */ + @Nullable + private Pair<Integer, ServiceProvider> getSingleProvider() { + final List<FingerprintSensorPropertiesInternal> properties = getSensorProperties(); + if (properties.size() != 1) { + return null; + } + + // Theoretically we can just return the first provider, but maybe this is easier to + // understand. + final int sensorId = properties.get(0).sensorId; + for (ServiceProvider provider : mServiceProviders) { + if (provider.containsSensor(sensorId)) { + return new Pair<>(sensorId, provider); + } + } + + Slog.e(TAG, "Single sensor, but provider not found"); + return null; + } + + @NonNull + private List<FingerprintSensorPropertiesInternal> getSensorProperties() { + final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>(); + + for (ServiceProvider provider : mServiceProviders) { + properties.addAll(provider.getSensorProperties()); + } + return properties; + } + + @NonNull + private List<Fingerprint> getEnrolledFingerprints(int userId, String opPackageName) { + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for getEnrolledFingerprints, caller: " + opPackageName); + return Collections.emptyList(); + } + + return provider.second.getEnrolledFingerprints(provider.first, userId); + } + /** * Checks for public API invocations to ensure that permissions, etc are granted/correct. */ 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 new file mode 100644 index 000000000000..d7338a02db67 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -0,0 +1,114 @@ +/* + * 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.biometrics.sensors.fingerprint; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.hardware.fingerprint.IUdfpsOverlayController; +import android.os.IBinder; +import android.view.Surface; + +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.LockoutTracker; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + +/** + * Superset of features/functionalities that HALs provide to the rest of the framework. This is + * more or less mapped to the public and private APIs that {@link FingerprintManager} provide, and + * is used at the system server layer to provide easy mapping between request and provider. + * + * Note that providers support both single-sensor and multi-sensor HALs. In either case, + * {@link FingerprintService} must ensure that providers are only requested to perform operations + * on sensors that they own. + * + * For methods other than {@link #containsSensor(int)}, the caller must ensure that the sensorId + * passed in is supported by the provider. For example, + * if (serviceProvider.containsSensor(sensorId)) { + * serviceProvider.operation(sensorId, ...); + * } + * + * For operations that are supported by some providers but not others, clients are required + * to check (e.g. via {@link FingerprintManager#getSensorPropertiesInternal()}) to ensure that the + * code path isn't taken. ServiceProviders will provide a no-op for unsupported operations to + * fail safely. + */ +@SuppressWarnings("deprecation") +public interface ServiceProvider { + /** + * Checks if the specified sensor is owned by this provider. + */ + boolean containsSensor(int sensorId); + + @NonNull List<FingerprintSensorPropertiesInternal> getSensorProperties(); + + void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken); + + void scheduleGenerateChallenge(int sensorId, @NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, String opPackageName); + + void scheduleRevokeChallenge(int sensorId, @NonNull IBinder token, + @NonNull String opPackageName); + + void scheduleEnroll(int sensorId, @NonNull IBinder token, byte[] hardwareAuthToken, int userId, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, + @Nullable Surface surface); + + void cancelEnrollment(int sensorId, @NonNull IBinder token); + + void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId, + @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName, + @Nullable Surface surface, int statsClient); + + void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId, + int cookie, @NonNull ClientMonitorCallbackConverter callback, + @NonNull String opPackageName, boolean restricted, int statsClient, boolean isKeyguard); + + void startPreparedClient(int sensorId, int cookie); + + void cancelAuthentication(int sensorId, @NonNull IBinder token); + + void scheduleRemove(int sensorId, @NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId, + @NonNull String opPackageName); + + boolean isHardwareDetected(int sensorId); + + void rename(int sensorId, int fingerId, int userId, @NonNull String name); + + @NonNull List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId); + + @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId); + + long getAuthenticatorId(int sensorId, int userId); + + void onFingerDown(int sensorId, int x, int y, float minor, float major); + + void onFingerUp(int sensorId); + + void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller); + + void dumpProto(int sensorId, @NonNull FileDescriptor fd); + + void dumpInternal(int sensorId, @NonNull PrintWriter pw); +} 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 c87bfec85dc3..f890f57eb47b 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 @@ -33,6 +33,7 @@ import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintSensorProperties; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Handler; @@ -66,6 +67,7 @@ import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.RemovalConsumer; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; +import com.android.server.biometrics.sensors.fingerprint.ServiceProvider; import org.json.JSONArray; import org.json.JSONException; @@ -83,14 +85,14 @@ import java.util.Map; * Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or * its extended minor versions. */ -public class Fingerprint21 implements IHwBinder.DeathRecipient { +public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider { private static final String TAG = "Fingerprint21"; private static final int ENROLL_TIMEOUT_SEC = 60; final Context mContext; private final IActivityTaskManager mActivityTaskManager; - private final FingerprintSensorProperties mSensorProperties; + @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties; private final BiometricScheduler mScheduler; private final Handler mHandler; private final LockoutResetDispatcher mLockoutResetDispatcher; @@ -346,7 +348,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { final int maxEnrollmentsPerUser = mContext.getResources() .getInteger(R.integer.config_fingerprintMaxTemplatesPerUser); - mSensorProperties = new FingerprintSensorProperties(sensorId, + mSensorProperties = new FingerprintSensorPropertiesInternal(sensorId, Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser, sensorType, resetLockoutRequiresHardwareAuthToken); } @@ -435,9 +437,6 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { @Nullable IUdfpsOverlayController getUdfpsOverlayController() { return mUdfpsOverlayController; } - @LockoutTracker.LockoutMode public int getLockoutModeForUser(int userId) { - return mLockoutTracker.getLockoutModeForUser(userId); - } private void scheduleLoadAuthenticatorIds() { // Note that this can be performed on the scheduler (as opposed to being done immediately @@ -466,7 +465,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { * correct. */ private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) { - final boolean hasEnrolled = !getEnrolledFingerprints(targetUserId).isEmpty(); + final boolean hasEnrolled = + !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty(); final FingerprintUpdateActiveUserClient client = new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorProperties.sensorId, mCurrentUserId, @@ -481,7 +481,21 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { }); } - public void scheduleResetLockout(int userId) { + @Override + public boolean containsSensor(int sensorId) { + return mSensorProperties.sensorId == sensorId; + } + + @Override + @NonNull + public List<FingerprintSensorPropertiesInternal> getSensorProperties() { + final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>(); + properties.add(mSensorProperties); + return properties; + } + + @Override + public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) { // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler // thread. mHandler.post(() -> { @@ -489,7 +503,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { }); } - public void scheduleGenerateChallenge(@NonNull IBinder token, + @Override + public void scheduleGenerateChallenge(int sensorId, @NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { final FingerprintGenerateChallengeClient client = @@ -500,7 +515,9 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { }); } - public void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String opPackageName) { + @Override + public void scheduleRevokeChallenge(int sensorId, @NonNull IBinder token, + @NonNull String opPackageName) { mHandler.post(() -> { final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient( mContext, mLazyDaemon, token, opPackageName, mSensorProperties.sensorId); @@ -508,7 +525,9 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { }); } - public void scheduleEnroll(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, + @Override + public void scheduleEnroll(int sensorId, @NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, @Nullable Surface surface) { mHandler.post(() -> { @@ -531,13 +550,15 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { }); } - public void cancelEnrollment(@NonNull IBinder token) { + @Override + public void cancelEnrollment(int sensorId, @NonNull IBinder token) { mHandler.post(() -> { mScheduler.cancelEnrollment(token); }); } - public void scheduleFingerDetect(@NonNull IBinder token, int userId, + @Override + public void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId, @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName, @Nullable Surface surface, int statsClient) { mHandler.post(() -> { @@ -552,8 +573,9 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { }); } - public void scheduleAuthenticate(@NonNull IBinder token, long operationId, int userId, - int cookie, @NonNull ClientMonitorCallbackConverter listener, + @Override + public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, + int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName, boolean restricted, int statsClient, boolean isKeyguard) { mHandler.post(() -> { @@ -569,19 +591,22 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { }); } - public void startPreparedClient(int cookie) { + @Override + public void startPreparedClient(int sensorId, int cookie) { mHandler.post(() -> { mScheduler.startPreparedClient(cookie); }); } - public void cancelAuthentication(@NonNull IBinder token) { + @Override + public void cancelAuthentication(int sensorId, @NonNull IBinder token) { mHandler.post(() -> { mScheduler.cancelAuthentication(token); }); } - public void scheduleRemove(@NonNull IBinder token, + @Override + public void scheduleRemove(int sensorId, @NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId, @NonNull String opPackageName) { mHandler.post(() -> { @@ -599,7 +624,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final List<Fingerprint> enrolledList = getEnrolledFingerprints(userId); + final List<Fingerprint> enrolledList = getEnrolledFingerprints( + mSensorProperties.sensorId, userId); final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient( mContext, mLazyDaemon, userId, mContext.getOpPackageName(), mSensorProperties.sensorId, enrolledList, FingerprintUtils.getInstance(), @@ -608,30 +634,37 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { }); } - public boolean isHardwareDetected() { + @Override + public boolean isHardwareDetected(int sensorId) { final IBiometricsFingerprint daemon = getDaemon(); return daemon != null; } - @NonNull public FingerprintSensorProperties getFingerprintSensorProperties() { - return mSensorProperties; - } - - public void rename(int fingerId, int userId, String name) { + @Override + public void rename(int sensorId, int fingerId, int userId, @NonNull String name) { mHandler.post(() -> { FingerprintUtils.getInstance().renameBiometricForUser(mContext, userId, fingerId, name); }); } - public List<Fingerprint> getEnrolledFingerprints(int userId) { + @Override + @NonNull + public List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId) { return FingerprintUtils.getInstance().getBiometricsForUser(mContext, userId); } - public long getAuthenticatorId(int userId) { + @Override + @LockoutTracker.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) { + return mLockoutTracker.getLockoutModeForUser(userId); + } + + @Override + public long getAuthenticatorId(int sensorId, int userId) { return mAuthenticatorIds.get(userId); } - public void onFingerDown(int x, int y, float minor, float major) { + @Override + public void onFingerDown(int sensorId, int x, int y, float minor, float major) { final ClientMonitor<?> client = mScheduler.getCurrentClient(); if (!(client instanceof Udfps)) { Slog.w(TAG, "onFingerDown received during client: " + client); @@ -641,7 +674,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { udfps.onFingerDown(x, y, minor, major); } - public void onFingerUp() { + @Override + public void onFingerUp(int sensorId) { final ClientMonitor<?> client = mScheduler.getCurrentClient(); if (!(client instanceof Udfps)) { Slog.w(TAG, "onFingerDown received during client: " + client); @@ -651,11 +685,13 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { udfps.onFingerUp(); } - public void setUdfpsOverlayController(IUdfpsOverlayController controller) { + @Override + public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) { mUdfpsOverlayController = controller; } - public void dumpProto(FileDescriptor fd) { + @Override + public void dumpProto(int sensorId, FileDescriptor fd) { PerformanceTracker tracker = PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId); @@ -695,7 +731,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient { tracker.clear(); } - public void dumpInternal(@NonNull PrintWriter pw) { + @Override + public void dumpInternal(int sensorId, @NonNull PrintWriter pw) { PerformanceTracker performanceTracker = PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index 6d8f241adbdc..5dda5a8495c2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -26,6 +26,7 @@ import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; import android.hardware.fingerprint.FingerprintSensorProperties; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Handler; import android.os.IBinder; @@ -46,6 +47,7 @@ import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import java.util.ArrayList; +import java.util.List; import java.util.Random; /** @@ -123,7 +125,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage @NonNull private final TestableBiometricScheduler mScheduler; @NonNull private final Handler mHandler; - @NonNull private final FingerprintSensorProperties mSensorProperties; + @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties; @NonNull private final MockHalResultController mMockHalResultController; @NonNull private final TrustManager mTrustManager; @NonNull private final SparseBooleanArray mUserHasTrust; @@ -397,8 +399,9 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage // Schedule this only after we invoke onClientFinished for the previous client, so that // internal preemption logic is not run. - mFingerprint21.scheduleAuthenticate(token, operationId, user, cookie, - listener, opPackageName, restricted, statsClient, isKeyguard); + mFingerprint21.scheduleAuthenticate(mFingerprint21.mSensorProperties.sensorId, token, + operationId, user, cookie, listener, opPackageName, restricted, statsClient, + isKeyguard); } } @@ -416,7 +419,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage final boolean resetLockoutRequiresHardwareAuthToken = false; final int maxTemplatesAllowed = mContext.getResources() .getInteger(R.integer.config_fingerprintMaxTemplatesPerUser); - mSensorProperties = new FingerprintSensorProperties(sensorId, + mSensorProperties = new FingerprintSensorPropertiesInternal(sensorId, Utils.authenticatorStrengthToPropertyStrength(strength), maxTemplatesAllowed, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, resetLockoutRequiresHardwareAuthToken); @@ -451,12 +454,14 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage @Override @NonNull - public FingerprintSensorProperties getFingerprintSensorProperties() { - return mSensorProperties; + public List<FingerprintSensorPropertiesInternal> getSensorProperties() { + final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>(); + properties.add(mSensorProperties); + return properties; } @Override - public void onFingerDown(int x, int y, float minor, float major) { + public void onFingerDown(int sensorId, int x, int y, float minor, float major) { mHandler.post(() -> { Slog.d(TAG, "onFingerDown"); final AuthenticationConsumer lastAuthenticatedConsumer = @@ -503,7 +508,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage } @Override - public void onFingerUp() { + public void onFingerUp(int sensorId) { mHandler.post(() -> { Slog.d(TAG, "onFingerUp"); @@ -558,7 +563,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage // Things can happen before SysUI loads and sets the controller. if (controller != null) { Slog.d(TAG, "setDebugMessage: " + message); - controller.setDebugMessage(message); + controller.setDebugMessage(mSensorProperties.sensorId, message); } } catch (RemoteException e) { Slog.e(TAG, "Remote exception when sending message: " + message, e); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index 8087e15b540d..0658f957fecb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -79,7 +79,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi if (authenticated) { resetFailedAttempts(getTargetUserId()); - UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController); + UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); mCallback.onClientFinished(this, true /* success */); } else { final @LockoutTracker.LockoutMode int lockoutMode = @@ -92,7 +92,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi // Send the error, but do not invoke the FinishCallback yet. Since lockout is not // controlled by the HAL, the framework must stop the sensor before finishing the // client. - UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController); + UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */); cancel(); } @@ -111,7 +111,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(mUdfpsOverlayController); + UdfpsHelper.showUdfpsOverlay(getSensorId(), mUdfpsOverlayController); try { // GroupId was never used. In fact, groupId is always the same as userId. getFreshDaemon().authenticate(mOperationId, getTargetUserId()); @@ -119,14 +119,14 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi Slog.e(TAG, "Remote exception when requesting auth", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); - UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController); + UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); mCallback.onClientFinished(this, false /* success */); } } @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController); + UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); try { getFreshDaemon().cancel(); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java index 5865617f4a44..cad2214891a7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java @@ -61,7 +61,7 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController); + UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); try { getFreshDaemon().cancel(); } catch (RemoteException e) { @@ -80,14 +80,14 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(mUdfpsOverlayController); + UdfpsHelper.showUdfpsOverlay(getSensorId(), mUdfpsOverlayController); try { getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId()); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting auth", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); - UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController); + UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); mCallback.onClientFinished(this, false /* success */); } } 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 1b9fae9cf91a..b1030bf367e8 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 @@ -70,7 +70,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(mUdfpsOverlayController); + UdfpsHelper.showUdfpsOverlay(getSensorId(), mUdfpsOverlayController); try { // GroupId was never used. In fact, groupId is always the same as userId. getFreshDaemon().enroll(mHardwareAuthToken, getTargetUserId(), mTimeoutSec); @@ -78,14 +78,14 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint Slog.e(TAG, "Remote exception when requesting enroll", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); - UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController); + UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); mCallback.onClientFinished(this, false /* success */); } } @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController); + UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); try { getFreshDaemon().cancel(); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/UdfpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/UdfpsHelper.java index c71ecbf7577d..0f1d6b462cac 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/UdfpsHelper.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/UdfpsHelper.java @@ -62,23 +62,25 @@ public class UdfpsHelper { } } - static void showUdfpsOverlay(@Nullable IUdfpsOverlayController udfpsOverlayController) { + static void showUdfpsOverlay(int sensorId, + @Nullable IUdfpsOverlayController udfpsOverlayController) { if (udfpsOverlayController == null) { return; } try { - udfpsOverlayController.showUdfpsOverlay(); + udfpsOverlayController.showUdfpsOverlay(sensorId); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e); } } - static void hideUdfpsOverlay(@Nullable IUdfpsOverlayController udfpsOverlayController) { + static void hideUdfpsOverlay(int sensorId, + @Nullable IUdfpsOverlayController udfpsOverlayController) { if (udfpsOverlayController == null) { return; } try { - udfpsOverlayController.hideUdfpsOverlay(); + udfpsOverlayController.hideUdfpsOverlay(sensorId); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e); } diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 5d2f51230c12..507600783aa4 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -836,7 +836,7 @@ public class ClipboardService extends SystemService { return; } if (Settings.Global.getInt(getContext().getContentResolver(), - "clipboard_access_toast_enabled", 0) == 0) { + "clipboard_access_toast_enabled", 1) == 0) { return; } // Don't notify if the app accessing the clipboard is the same as the current owner. diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 99dc58e20209..14b34780bfc9 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -77,6 +77,7 @@ import android.net.ipsec.ike.ChildSessionParams; import android.net.ipsec.ike.IkeSession; import android.net.ipsec.ike.IkeSessionCallback; import android.net.ipsec.ike.IkeSessionParams; +import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.os.Binder; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -142,6 +143,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; /** @@ -2301,7 +2303,7 @@ public class Vpn { void onChildTransformCreated( @NonNull Network network, @NonNull IpSecTransform transform, int direction); - void onSessionLost(@NonNull Network network); + void onSessionLost(@NonNull Network network, @Nullable Exception exception); } /** @@ -2458,7 +2460,7 @@ public class Vpn { networkAgent.sendLinkProperties(lp); } catch (Exception e) { Log.d(TAG, "Error in ChildOpened for network " + network, e); - onSessionLost(network); + onSessionLost(network, e); } } @@ -2488,7 +2490,7 @@ public class Vpn { mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform); } catch (IOException e) { Log.d(TAG, "Transform application failed for network " + network, e); - onSessionLost(network); + onSessionLost(network, e); } } @@ -2546,11 +2548,20 @@ public class Vpn { Log.d(TAG, "Ike Session started for network " + network); } catch (Exception e) { Log.i(TAG, "Setup failed for network " + network + ". Aborting", e); - onSessionLost(network); + onSessionLost(network, e); } }); } + /** Marks the state as FAILED, and disconnects. */ + private void markFailedAndDisconnect(Exception exception) { + synchronized (Vpn.this) { + updateState(DetailedState.FAILED, exception.getMessage()); + } + + disconnectVpnRunner(); + } + /** * Handles loss of a session * @@ -2560,7 +2571,7 @@ public class Vpn { * <p>This method MUST always be called on the mExecutor thread in order to ensure * consistency of the Ikev2VpnRunner fields. */ - public void onSessionLost(@NonNull Network network) { + public void onSessionLost(@NonNull Network network, @Nullable Exception exception) { if (!isActiveNetwork(network)) { Log.d(TAG, "onSessionLost() called for obsolete network " + network); @@ -2572,6 +2583,27 @@ public class Vpn { return; } + if (exception instanceof IkeProtocolException) { + final IkeProtocolException ikeException = (IkeProtocolException) exception; + + switch (ikeException.getErrorType()) { + case IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN: // Fallthrough + case IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD: // Fallthrough + case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED: // Fallthrough + case IkeProtocolException.ERROR_TYPE_SINGLE_PAIR_REQUIRED: // Fallthrough + case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough + case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE: + // All the above failures are configuration errors, and are terminal + markFailedAndDisconnect(exception); + return; + // All other cases possibly recoverable. + } + } else if (exception instanceof IllegalArgumentException) { + // Failed to build IKE/ChildSessionParams; fatal profile configuration error + markFailedAndDisconnect(exception); + return; + } + mActiveNetwork = null; // Close all obsolete state, but keep VPN alive incase a usable network comes up. @@ -2621,12 +2653,18 @@ public class Vpn { } /** - * Cleans up all Ikev2VpnRunner internal state + * Disconnects and shuts down this VPN. + * + * <p>This method resets all internal Ikev2VpnRunner state, but unless called via + * VpnRunner#exit(), this Ikev2VpnRunner will still be listed as the active VPN of record + * until the next VPN is started, or the Ikev2VpnRunner is explicitly exited. This is + * necessary to ensure that the detailed state is shown in the Settings VPN menus; if the + * active VPN is cleared, Settings VPNs will not show the resultant state or errors. * * <p>This method MUST always be called on the mExecutor thread in order to ensure * consistency of the Ikev2VpnRunner fields. */ - private void shutdownVpnRunner() { + private void disconnectVpnRunner() { mActiveNetwork = null; mIsRunning = false; @@ -2640,9 +2678,13 @@ public class Vpn { @Override public void exitVpnRunner() { - mExecutor.execute(() -> { - shutdownVpnRunner(); - }); + try { + mExecutor.execute(() -> { + disconnectVpnRunner(); + }); + } catch (RejectedExecutionException ignored) { + // The Ikev2VpnRunner has already shut down. + } } } diff --git a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java index 103f659cc258..fa03e59f2f2e 100644 --- a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java +++ b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java @@ -60,12 +60,12 @@ import android.net.ipsec.ike.IkeTrafficSelector; import android.net.ipsec.ike.TunnelModeChildSessionParams; import android.net.ipsec.ike.exceptions.IkeException; import android.net.ipsec.ike.exceptions.IkeProtocolException; -import android.net.util.IpRange; import android.system.OsConstants; import android.util.Log; import com.android.internal.net.VpnProfile; import com.android.internal.util.HexDump; +import com.android.net.module.util.IpRange; import java.net.Inet4Address; import java.net.Inet6Address; @@ -270,13 +270,13 @@ public class VpnIkev2Utils { @Override public void onClosed() { Log.d(mTag, "IkeClosed for network " + mNetwork); - mCallback.onSessionLost(mNetwork); // Server requested session closure. Retry? + mCallback.onSessionLost(mNetwork, null); // Server requested session closure. Retry? } @Override public void onClosedExceptionally(@NonNull IkeException exception) { Log.d(mTag, "IkeClosedExceptionally for network " + mNetwork, exception); - mCallback.onSessionLost(mNetwork); + mCallback.onSessionLost(mNetwork, exception); } @Override @@ -306,13 +306,13 @@ public class VpnIkev2Utils { @Override public void onClosed() { Log.d(mTag, "ChildClosed for network " + mNetwork); - mCallback.onSessionLost(mNetwork); + mCallback.onSessionLost(mNetwork, null); } @Override public void onClosedExceptionally(@NonNull IkeException exception) { Log.d(mTag, "ChildClosedExceptionally for network " + mNetwork, exception); - mCallback.onSessionLost(mNetwork); + mCallback.onSessionLost(mNetwork, exception); } @Override @@ -349,7 +349,7 @@ public class VpnIkev2Utils { @Override public void onLost(@NonNull Network network) { Log.d(mTag, "Tearing down; lost network: " + network); - mCallback.onSessionLost(network); + mCallback.onSessionLost(network, null); } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java new file mode 100644 index 000000000000..f4f77db91500 --- /dev/null +++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java @@ -0,0 +1,149 @@ +/* + * 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.annotation.NonNull; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.display.DisplayManagerService.SyncRoot; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Container for all the display devices present in the system. If an object wants to get events + * about all the DisplayDevices without needing to listen to all of the DisplayAdapters, they can + * listen and interact with the instance of this class. + * <p> + * The collection of {@link DisplayDevice}s and their usage is protected by the provided + * {@link DisplayManagerService.SyncRoot} lock object. + */ +class DisplayDeviceRepository implements DisplayAdapter.Listener { + private static final String TAG = "DisplayDeviceRepository"; + + public static final int DISPLAY_DEVICE_EVENT_ADDED = 1; + public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2; + public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3; + + /** + * List of all currently connected display devices. Indexed by the displayId. + * TODO: multi-display - break the notion that this is indexed by displayId. + */ + @GuardedBy("mSyncRoot") + private final List<DisplayDevice> mDisplayDevices = new ArrayList<>(); + + /** Listener for {link DisplayDevice} events. */ + private final Listener mListener; + + /** Global lock object from {@link DisplayManagerService}. */ + private final SyncRoot mSyncRoot; + + DisplayDeviceRepository(@NonNull SyncRoot syncRoot, @NonNull Listener listener) { + mSyncRoot = syncRoot; + mListener = listener; + } + + @Override + public void onDisplayDeviceEvent(DisplayDevice device, int event) { + switch (event) { + case DISPLAY_DEVICE_EVENT_ADDED: + handleDisplayDeviceAdded(device); + break; + + case DISPLAY_DEVICE_EVENT_CHANGED: + handleDisplayDeviceChanged(device); + break; + + case DISPLAY_DEVICE_EVENT_REMOVED: + handleDisplayDeviceRemoved(device); + break; + } + } + + @Override + public void onTraversalRequested() { + mListener.onTraversalRequested(); + } + + public boolean containsLocked(DisplayDevice d) { + return mDisplayDevices.contains(d); + } + + public int sizeLocked() { + return mDisplayDevices.size(); + } + + public void forEachLocked(Consumer<DisplayDevice> consumer) { + final int count = mDisplayDevices.size(); + for (int i = 0; i < count; i++) { + consumer.accept(mDisplayDevices.get(i)); + } + } + + private void handleDisplayDeviceAdded(DisplayDevice device) { + synchronized (mSyncRoot) { + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + if (mDisplayDevices.contains(device)) { + Slog.w(TAG, "Attempted to add already added display device: " + info); + return; + } + Slog.i(TAG, "Display device added: " + info); + device.mDebugLastLoggedDeviceInfo = info; + + mDisplayDevices.add(device); + mListener.onDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED); + } + } + + private void handleDisplayDeviceChanged(DisplayDevice device) { + synchronized (mSyncRoot) { + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + if (!mDisplayDevices.contains(device)) { + Slog.w(TAG, "Attempted to change non-existent display device: " + info); + return; + } + mListener.onDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED); + } + } + + private void handleDisplayDeviceRemoved(DisplayDevice device) { + synchronized (mSyncRoot) { + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + if (!mDisplayDevices.remove(device)) { + Slog.w(TAG, "Attempted to remove non-existent display device: " + info); + return; + } + + Slog.i(TAG, "Display device removed: " + info); + device.mDebugLastLoggedDeviceInfo = info; + mListener.onDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED); + } + } + + /** + * Listens to {@link DisplayDevice} events from {@link DisplayDeviceRepository}. + */ + interface Listener { + void onDisplayDeviceEventLocked(DisplayDevice device, int event); + + // TODO: multi-display - Try to remove the need for requestTraversal...it feels like + // a shoe-horned method for a shoe-horned feature. + void onTraversalRequested(); + }; +} diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 97c4cf531a53..597f49c63193 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -131,7 +131,8 @@ import java.util.concurrent.CopyOnWriteArrayList; * </p><p> * Display adapters are only weakly coupled to the display manager service. * Display adapters communicate changes in display device state to the display manager - * service asynchronously via a {@link DisplayAdapter.Listener} registered + * service asynchronously via a {@link DisplayAdapter.Listener}, and through + * the {@link DisplayDeviceRepository.Listener}, which is ultimately registered * by the display manager service. This separation of concerns is important for * two main reasons. First, it neatly encapsulates the responsibilities of these * two classes: display adapters handle individual display devices whereas @@ -180,7 +181,7 @@ public final class DisplayManagerService extends SystemService { private final Context mContext; private final DisplayManagerHandler mHandler; private final Handler mUiHandler; - private final DisplayAdapterListener mDisplayAdapterListener; + private final DisplayDeviceListener mDisplayDeviceListener; private final DisplayModeDirector mDisplayModeDirector; private WindowManagerInternal mWindowManagerInternal; private InputManagerInternal mInputManagerInternal; @@ -215,8 +216,7 @@ public final class DisplayManagerService extends SystemService { // List of all currently registered display adapters. private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>(); - // List of all currently connected display devices. - private final ArrayList<DisplayDevice> mDisplayDevices = new ArrayList<DisplayDevice>(); + private final DisplayDeviceRepository mDisplayDeviceRepo; // List of all logical displays indexed by logical display id. // Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache. @@ -331,7 +331,8 @@ public final class DisplayManagerService extends SystemService { mContext = context; mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper()); mUiHandler = UiThread.getHandler(); - mDisplayAdapterListener = new DisplayAdapterListener(); + mDisplayDeviceListener = new DisplayDeviceListener(); + mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mDisplayDeviceListener); mDisplayModeDirector = new DisplayModeDirector(context, mHandler); mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false); Resources resources = mContext.getResources(); @@ -469,6 +470,11 @@ public final class DisplayManagerService extends SystemService { return mHandler; } + @VisibleForTesting + DisplayDeviceRepository getDisplayDeviceRepository() { + return mDisplayDeviceRepo; + } + private void loadStableDisplayValuesLocked() { final Point size = mPersistentDataStore.getStableDisplaySize(); if (size.x > 0 && size.y > 0) { @@ -818,7 +824,17 @@ public final class DisplayManagerService extends SystemService { return -1; } - handleDisplayDeviceAddedLocked(device); + // DisplayDevice events are handled manually for Virtual Displays. + // TODO: multi-display Fix this so that generic add/remove events are not handled in a + // different code path for virtual displays. Currently this happens so that we can + // return a valid display ID synchronously upon successful Virtual Display creation. + // This code can run on any binder thread, while onDisplayDeviceAdded() callbacks are + // called on the DisplayThread (which we don't want to wait for?). + // One option would be to actually wait here on the binder thread + // to be notified when the virtual display is created (or failed). + mDisplayDeviceRepo.onDisplayDeviceEvent(device, + DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); + LogicalDisplay display = findLogicalDisplayForDeviceLocked(device); if (display != null) { return display.getDisplayIdLocked(); @@ -828,7 +844,8 @@ public final class DisplayManagerService extends SystemService { Slog.w(TAG, "Rejecting request to create virtual display " + "because the logical display was not created."); mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder()); - handleDisplayDeviceRemovedLocked(device); + mDisplayDeviceRepo.onDisplayDeviceEvent(device, + DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED); } return -1; } @@ -863,7 +880,9 @@ public final class DisplayManagerService extends SystemService { DisplayDevice device = mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken); if (device != null) { - handleDisplayDeviceRemovedLocked(device); + // TODO - handle virtual displays the same as other display adapters. + mDisplayDeviceRepo.onDisplayDeviceEvent(device, + DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED); } } } @@ -883,7 +902,7 @@ public final class DisplayManagerService extends SystemService { synchronized (mSyncRoot) { // main display adapter registerDisplayAdapterLocked(new LocalDisplayAdapter( - mSyncRoot, mContext, mHandler, mDisplayAdapterListener)); + mSyncRoot, mContext, mHandler, mDisplayDeviceRepo)); // Standalone VR devices rely on a virtual display as their primary display for // 2D UI. We register virtual display adapter along side the main display adapter @@ -891,7 +910,7 @@ public final class DisplayManagerService extends SystemService { // early apps like SetupWizard/Launcher. In particular, SUW is displayed using // the virtual display inside VR before any VR-specific apps even run. mVirtualDisplayAdapter = mInjector.getVirtualDisplayAdapter(mSyncRoot, mContext, - mHandler, mDisplayAdapterListener); + mHandler, mDisplayDeviceRepo); if (mVirtualDisplayAdapter != null) { registerDisplayAdapterLocked(mVirtualDisplayAdapter); } @@ -909,7 +928,7 @@ public final class DisplayManagerService extends SystemService { private void registerOverlayDisplayAdapterLocked() { registerDisplayAdapterLocked(new OverlayDisplayAdapter( - mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler)); + mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mUiHandler)); } private void registerWifiDisplayAdapterLocked() { @@ -917,7 +936,7 @@ public final class DisplayManagerService extends SystemService { com.android.internal.R.bool.config_enableWifiDisplay) || SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) { mWifiDisplayAdapter = new WifiDisplayAdapter( - mSyncRoot, mContext, mHandler, mDisplayAdapterListener, + mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mPersistentDataStore); registerDisplayAdapterLocked(mWifiDisplayAdapter); } @@ -946,15 +965,6 @@ public final class DisplayManagerService extends SystemService { } private void handleDisplayDeviceAddedLocked(DisplayDevice device) { - DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); - if (mDisplayDevices.contains(device)) { - Slog.w(TAG, "Attempted to add already added display device: " + info); - return; - } - Slog.i(TAG, "Display device added: " + info); - device.mDebugLastLoggedDeviceInfo = info; - - mDisplayDevices.add(device); LogicalDisplay display = addLogicalDisplayLocked(device); Runnable work = updateDisplayStateLocked(device); if (work != null) { @@ -966,45 +976,45 @@ public final class DisplayManagerService extends SystemService { @VisibleForTesting void handleDisplayDeviceChanged(DisplayDevice device) { synchronized (mSyncRoot) { - DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); - if (!mDisplayDevices.contains(device)) { - Slog.w(TAG, "Attempted to change non-existent display device: " + info); - return; - } + handleDisplayDeviceChangedLocked(device); + } + } - int diff = device.mDebugLastLoggedDeviceInfo.diff(info); - if (diff == DisplayDeviceInfo.DIFF_STATE) { - Slog.i(TAG, "Display device changed state: \"" + info.name - + "\", " + Display.stateToString(info.state)); - final Optional<Integer> viewportType = getViewportType(info); - if (viewportType.isPresent()) { - for (DisplayViewport d : mViewports) { - if (d.type == viewportType.get() && info.uniqueId.equals(d.uniqueId)) { - // Update display view port power state - d.isActive = Display.isActiveState(info.state); - } - } - if (mInputManagerInternal != null) { - mHandler.sendEmptyMessage(MSG_UPDATE_VIEWPORT); + private void handleDisplayDeviceChangedLocked(DisplayDevice device) { + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + + int diff = device.mDebugLastLoggedDeviceInfo.diff(info); + if (diff == DisplayDeviceInfo.DIFF_STATE) { + Slog.i(TAG, "Display device changed state: \"" + info.name + + "\", " + Display.stateToString(info.state)); + final Optional<Integer> viewportType = getViewportType(info); + if (viewportType.isPresent()) { + for (DisplayViewport d : mViewports) { + if (d.type == viewportType.get() && info.uniqueId.equals(d.uniqueId)) { + // Update display view port power state + d.isActive = Display.isActiveState(info.state); } } - } else if (diff != 0) { - Slog.i(TAG, "Display device changed: " + info); - } - if ((diff & DisplayDeviceInfo.DIFF_COLOR_MODE) != 0) { - try { - mPersistentDataStore.setColorMode(device, info.colorMode); - } finally { - mPersistentDataStore.saveIfNeeded(); + if (mInputManagerInternal != null) { + mHandler.sendEmptyMessage(MSG_UPDATE_VIEWPORT); } } - device.mDebugLastLoggedDeviceInfo = info; - - device.applyPendingDisplayDeviceInfoChangesLocked(); - if (updateLogicalDisplaysLocked()) { - scheduleTraversalLocked(false); + } else if (diff != 0) { + Slog.i(TAG, "Display device changed: " + info); + } + if ((diff & DisplayDeviceInfo.DIFF_COLOR_MODE) != 0) { + try { + mPersistentDataStore.setColorMode(device, info.colorMode); + } finally { + mPersistentDataStore.saveIfNeeded(); } } + device.mDebugLastLoggedDeviceInfo = info; + + device.applyPendingDisplayDeviceInfoChangesLocked(); + if (updateLogicalDisplaysLocked()) { + scheduleTraversalLocked(false); + } } private void handleDisplayDeviceRemoved(DisplayDevice device) { @@ -1014,15 +1024,6 @@ public final class DisplayManagerService extends SystemService { } private void handleDisplayDeviceRemovedLocked(DisplayDevice device) { - DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); - if (!mDisplayDevices.remove(device)) { - Slog.w(TAG, "Attempted to remove non-existent display device: " + info); - return; - } - - Slog.i(TAG, "Display device removed: " + info); - device.mDebugLastLoggedDeviceInfo = info; - updateLogicalDisplaysLocked(); scheduleTraversalLocked(false); } @@ -1043,14 +1044,12 @@ public final class DisplayManagerService extends SystemService { } private void applyGlobalDisplayStateLocked(List<Runnable> workQueue) { - final int count = mDisplayDevices.size(); - for (int i = 0; i < count; i++) { - DisplayDevice device = mDisplayDevices.get(i); + mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> { Runnable runnable = updateDisplayStateLocked(device); if (runnable != null) { workQueue.add(runnable); } - } + }); } private Runnable updateDisplayStateLocked(DisplayDevice device) { @@ -1058,6 +1057,8 @@ public final class DisplayManagerService extends SystemService { // by the display power controller (if known). DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) { + // TODO - multi-display - The rules regarding what display state to apply to each + // display will depend on the configuration/mapping of logical displays. return device.requestDisplayStateLocked( mGlobalDisplayState, mGlobalDisplayBrightness); } @@ -1085,7 +1086,7 @@ public final class DisplayManagerService extends SystemService { final int layerStack = assignLayerStackLocked(displayId); LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); - display.updateLocked(mDisplayDevices); + display.updateLocked(mDisplayDeviceRepo); if (!display.isValidLocked()) { // This should never happen currently. Slog.w(TAG, "Ignoring display device because the logical display " @@ -1248,7 +1249,7 @@ public final class DisplayManagerService extends SystemService { mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked()); display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo); - display.updateLocked(mDisplayDevices); + display.updateLocked(mDisplayDeviceRepo); if (!display.isValidLocked()) { mLogicalDisplays.removeAt(i); handleLogicalDisplayRemoved(displayId); @@ -1276,12 +1277,10 @@ public final class DisplayManagerService extends SystemService { clearViewportsLocked(); // Configure each display device. - final int count = mDisplayDevices.size(); - for (int i = 0; i < count; i++) { - DisplayDevice device = mDisplayDevices.get(i); + mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> { configureDisplayLocked(t, device); device.performTraversalLocked(t); - } + }); // Tell the input system about these new viewports. if (mInputManagerInternal != null) { @@ -1714,11 +1713,11 @@ public final class DisplayManagerService extends SystemService { } pw.println(); - pw.println("Display Devices: size=" + mDisplayDevices.size()); - for (DisplayDevice device : mDisplayDevices) { + pw.println("Display Devices: size=" + mDisplayDeviceRepo.sizeLocked()); + mDisplayDeviceRepo.forEachLocked(device -> { pw.println(" " + device.getDisplayDeviceInfoLocked()); device.dumpLocked(ipw); - } + }); final int logicalDisplayCount = mLogicalDisplays.size(); pw.println(); @@ -1864,20 +1863,20 @@ public final class DisplayManagerService extends SystemService { } } - private final class DisplayAdapterListener implements DisplayAdapter.Listener { + private final class DisplayDeviceListener implements DisplayDeviceRepository.Listener { @Override - public void onDisplayDeviceEvent(DisplayDevice device, int event) { + public void onDisplayDeviceEventLocked(DisplayDevice device, int event) { switch (event) { - case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED: - handleDisplayDeviceAdded(device); + case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_ADDED: + handleDisplayDeviceAddedLocked(device); break; - case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED: - handleDisplayDeviceChanged(device); + case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_CHANGED: + handleDisplayDeviceChangedLocked(device); break; - case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED: - handleDisplayDeviceRemoved(device); + case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_REMOVED: + handleDisplayDeviceRemovedLocked(device); break; } } @@ -2573,9 +2572,10 @@ public final class DisplayManagerService extends SystemService { } } }; + LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY); + DisplayDevice defaultDevice = defaultDisplay.getPrimaryDisplayDeviceLocked(); mDisplayPowerController = new DisplayPowerController( - mContext, callbacks, handler, sensorManager, blanker, - mDisplayDevices.get(Display.DEFAULT_DISPLAY)); + mContext, callbacks, handler, sensorManager, blanker, defaultDevice); mSensorManager = sensorManager; } @@ -2689,9 +2689,7 @@ public final class DisplayManagerService extends SystemService { @Override public void onOverlayChanged() { synchronized (mSyncRoot) { - for (int i = 0; i < mDisplayDevices.size(); i++) { - mDisplayDevices.get(i).onOverlayChangedLocked(); - } + mDisplayDeviceRepo.forEachLocked(DisplayDevice::onOverlayChangedLocked); } } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 8556f084a072..bf8b891cffb8 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -28,7 +28,6 @@ import com.android.server.wm.utils.InsetUtils; import java.io.PrintWriter; import java.util.Arrays; -import java.util.List; import java.util.Objects; /** @@ -220,16 +219,16 @@ final class LogicalDisplay { * The logical display might become invalid if it is attached to a display device * that no longer exists. * - * @param devices The list of all connected display devices. + * @param deviceRepo Repository of active {@link DisplayDevice}s. */ - public void updateLocked(List<DisplayDevice> devices) { + public void updateLocked(DisplayDeviceRepository deviceRepo) { // Nothing to update if already invalid. if (mPrimaryDisplayDevice == null) { return; } // Check whether logical display has become invalid. - if (!devices.contains(mPrimaryDisplayDevice)) { + if (!deviceRepo.containsLocked(mPrimaryDisplayDevice)) { mPrimaryDisplayDevice = null; return; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index f2f6dbe9bde5..6e6d848c2acc 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -58,11 +58,6 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { // If true, turn off TV upon standby. False by default. private boolean mAutoTvOff; - // Local active port number used for Routing Control. - // Default 0 means HOME is the current active path. Temp solution only. - // TODO(amyjojo): adding system constants for input ports to TIF mapping. - private int mLocalActivePath = 0; - // Determines what action should be taken upon receiving Routing Control messages. @VisibleForTesting protected HdmiProperties.playback_device_action_on_routing_control_values @@ -438,16 +433,6 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { checkIfPendingActionsCleared(); } - private void routeToPort(int portId) { - // TODO(AMYJOJO): route to specific input of the port - mLocalActivePath = portId; - } - - @VisibleForTesting - protected int getLocalActivePath() { - return mLocalActivePath; - } - @Override protected void dump(final IndentingPrintWriter pw) { super.dump(pw); diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 7c98c6c7947b..cc8a330b5edb 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -46,6 +46,7 @@ import android.hardware.input.ITabletModeChangedListener; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.InputManager; import android.hardware.input.InputManagerInternal; +import android.hardware.input.InputManagerInternal.LidSwitchCallback; import android.hardware.input.KeyboardLayout; import android.hardware.input.TouchCalibration; import android.media.AudioManager; @@ -189,6 +190,10 @@ public class InputManagerService extends IInputManager.Stub private Map<IBinder, VibratorToken> mVibratorTokens = new ArrayMap<IBinder, VibratorToken>(); private int mNextVibratorTokenValue; + // State for lid switch + private final Object mLidSwitchLock = new Object(); + private List<LidSwitchCallback> mLidSwitchCallbacks = new ArrayList<>(); + // State for the currently installed input filter. final Object mInputFilterLock = new Object(); IInputFilter mInputFilter; // guarded by mInputFilterLock @@ -233,7 +238,7 @@ public class InputManagerService extends IInputManager.Stub private static native void nativeToggleCapsLock(long ptr, int deviceId); private static native void nativeDisplayRemoved(long ptr, int displayId); private static native void nativeSetInputDispatchMode(long ptr, boolean enabled, boolean frozen); - private static native void nativeSetSystemUiVisibility(long ptr, int visibility); + private static native void nativeSetSystemUiLightsOut(long ptr, boolean lightsOut); private static native void nativeSetFocusedApplication(long ptr, int displayId, InputApplicationHandle application); private static native void nativeSetFocusedDisplay(long ptr, int displayId); @@ -330,6 +335,9 @@ public class InputManagerService extends IInputManager.Stub public static final int SW_CAMERA_LENS_COVER_BIT = 1 << SW_CAMERA_LENS_COVER; public static final int SW_MUTE_DEVICE_BIT = 1 << SW_MUTE_DEVICE; + /** Indicates an open state for the lid switch. */ + public static final int SW_STATE_LID_OPEN = 0; + /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */ final boolean mUseDevInputEventForAudioJack; @@ -353,13 +361,33 @@ public class InputManagerService extends IInputManager.Stub } public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) { + if (mWindowManagerCallbacks != null) { + unregisterLidSwitchCallbackInternal(mWindowManagerCallbacks); + } mWindowManagerCallbacks = callbacks; + registerLidSwitchCallbackInternal(mWindowManagerCallbacks); } public void setWiredAccessoryCallbacks(WiredAccessoryCallbacks callbacks) { mWiredAccessoryCallbacks = callbacks; } + void registerLidSwitchCallbackInternal(@NonNull LidSwitchCallback callback) { + boolean lidOpen; + synchronized (mLidSwitchLock) { + mLidSwitchCallbacks.add(callback); + lidOpen = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY, SW_LID) + == SW_STATE_LID_OPEN; + } + callback.notifyLidSwitchChanged(0 /* whenNanos */, lidOpen); + } + + void unregisterLidSwitchCallbackInternal(@NonNull LidSwitchCallback callback) { + synchronized (mLidSwitchLock) { + mLidSwitchCallbacks.remove(callback); + } + } + public void start() { Slog.i(TAG, "Starting input manager"); nativeStart(mPtr); @@ -1560,8 +1588,8 @@ public class InputManagerService extends IInputManager.Stub nativeSetInputDispatchMode(mPtr, enabled, frozen); } - public void setSystemUiVisibility(int visibility) { - nativeSetSystemUiVisibility(mPtr, visibility); + public void setSystemUiLightsOut(boolean lightsOut) { + nativeSetSystemUiLightsOut(mPtr, lightsOut); } /** @@ -1934,6 +1962,7 @@ public class InputManagerService extends IInputManager.Stub synchronized (mInputFilterLock) { } synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */} synchronized (mGestureMonitorPidsLock) { /* Test if blocked by gesture monitor pids lock */} + synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ } nativeMonitor(mPtr); } @@ -1964,7 +1993,15 @@ public class InputManagerService extends IInputManager.Stub if ((switchMask & SW_LID_BIT) != 0) { final boolean lidOpen = ((switchValues & SW_LID_BIT) == 0); - mWindowManagerCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen); + + ArrayList<LidSwitchCallback> callbacksCopy; + synchronized (mLidSwitchLock) { + callbacksCopy = new ArrayList<>(mLidSwitchCallbacks); + } + for (int i = 0; i < callbacksCopy.size(); i++) { + LidSwitchCallback callbacks = callbacksCopy.get(i); + callbacks.notifyLidSwitchChanged(whenNanos, lidOpen); + } } if ((switchMask & SW_CAMERA_LENS_COVER_BIT) != 0) { @@ -2263,20 +2300,13 @@ public class InputManagerService extends IInputManager.Stub /** * Callback interface implemented by the Window Manager. */ - public interface WindowManagerCallbacks { + public interface WindowManagerCallbacks extends LidSwitchCallback { /** * This callback is invoked when the confuguration changes. */ public void notifyConfigurationChanged(); /** - * This callback is invoked when the lid switch changes state. - * @param whenNanos the time when the change occurred - * @param lidOpen true if the lid is open - */ - public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen); - - /** * This callback is invoked when the camera lens cover switch changes state. * @param whenNanos the time when the change occurred * @param lensCovered true is the lens is covered @@ -2603,6 +2633,16 @@ public class InputManagerService extends IInputManager.Stub @NonNull IBinder toChannelToken) { return InputManagerService.this.transferTouchFocus(fromChannelToken, toChannelToken); } + + @Override + public void registerLidSwitchCallback(LidSwitchCallback callbacks) { + registerLidSwitchCallbackInternal(callbacks); + } + + @Override + public void unregisterLidSwitchCallback(LidSwitchCallback callbacks) { + unregisterLidSwitchCallbackInternal(callbacks); + } } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index bba248c2ab74..2eccaf1434b4 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -17,6 +17,9 @@ package com.android.server.inputmethod; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.CLIENTS; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ELAPSED_REALTIME_NANOS; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.ENTRY; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -96,12 +99,15 @@ import android.text.style.SuggestionSpan; import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; +import android.util.Log; import android.util.LruCache; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; import android.util.SparseArray; +import android.util.imetracing.ImeTracing; +import android.util.proto.ProtoOutputStream; import android.view.ContextThemeWrapper; import android.view.DisplayInfo; import android.view.IWindowManager; @@ -1702,7 +1708,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Intent intent = new Intent(ACTION_SHOW_INPUT_METHOD_PICKER) .setPackage(mContext.getPackageName()); - mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); + mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_IMMUTABLE); mShowOngoingImeSwitcherForPhones = false; @@ -2530,7 +2537,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.input_method_binding_label); mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( - mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0)); + mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), + PendingIntent.FLAG_IMMUTABLE)); if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) { mLastBindTime = SystemClock.uptimeMillis(); @@ -3463,6 +3471,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final boolean sameWindowFocused = mCurFocusedWindow == windowToken; final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0; + final boolean startInputByWinGainedFocus = + (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0; + if (sameWindowFocused && isTextEditor) { if (DEBUG) { Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client @@ -3506,7 +3517,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub InputBindResult res = null; switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) { case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: - if (!isTextEditor || !doAutoShow) { + if (!sameWindowFocused && (!isTextEditor || !doAutoShow)) { if (LayoutParams.mayUseInputMethod(windowFlags)) { // There is no focus view, and this window will // be behind any soft input window, so hide the @@ -3555,7 +3566,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } break; case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: - if (isImeVisible()) { + if (!sameWindowFocused) { if (DEBUG) Slog.v(TAG, "Window asks to hide input"); hideCurrentInputLocked(mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE); @@ -3584,7 +3595,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) Slog.v(TAG, "Window asks to always show input"); if (InputMethodUtils.isSoftInputModeStateVisibleAllowed( unverifiedTargetSdkVersion, startInputFlags)) { - if (!isImeVisible()) { + if (!sameWindowFocused) { if (attribute != null) { res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute, startInputFlags, startInputReason); @@ -3603,17 +3614,26 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!didStart) { if (attribute != null) { - if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value() + if (sameWindowFocused) { + // On previous platforms, when Dialogs re-gained focus, the Activity behind + // would briefly gain focus first, and dismiss the IME. + // On R that behavior has been fixed, but unfortunately apps have come + // to rely on this behavior to hide the IME when the editor no longer has focus + // To maintain compatibility, we are now hiding the IME when we don't have + // an editor upon refocusing a window. + if (startInputByWinGainedFocus) { + hideCurrentInputLocked(mCurFocusedWindow, 0, null, + SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR); + } + res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute, + startInputFlags, startInputReason); + } else if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value() || (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) { res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute, startInputFlags, startInputReason); } else { res = InputBindResult.NO_EDITOR; } - } else if (sameWindowFocused) { - return new InputBindResult( - InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, - null, null, null, -1, null); } else { res = InputBindResult.NULL_EDITOR_INFO; } @@ -4018,6 +4038,55 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget(); } + /** + * Starting point for dumping the IME tracing information in proto format. + * + * @param clientProtoDump dump information from the IME client side + */ + @BinderThread + @Override + public void startProtoDump(byte[] clientProtoDump) { + if (!ImeTracing.getInstance().isAvailable() || !ImeTracing.getInstance().isEnabled()) { + return; + } + if (clientProtoDump == null && mCurClient == null) { + return; + } + + ProtoOutputStream proto = new ProtoOutputStream(); + final long token = proto.start(ENTRY); + proto.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos()); + // TODO: get server side dump + if (clientProtoDump != null) { + proto.write(CLIENTS, clientProtoDump); + } else { + IBinder client = null; + + synchronized (mMethodMap) { + if (mCurClient != null && mCurClient.client != null) { + client = mCurClient.client.asBinder(); + } + } + + if (client != null) { + try { + proto.write(CLIENTS, + TransferPipe.dumpAsync(client, ImeTracing.PROTO_ARG)); + } catch (IOException | RemoteException e) { + Log.e(TAG, "Exception while collecting client side ime dump", e); + } + } + } + proto.end(token); + ImeTracing.getInstance().addToBuffer(proto); + } + + @BinderThread + @Override + public boolean isImeTraceEnabled() { + return ImeTracing.getInstance().isEnabled(); + } + @BinderThread private void notifyUserAction(@NonNull IBinder token) { if (DEBUG) { @@ -5412,6 +5481,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mService.handleShellCommandSetInputMethod(this); case "reset": return mService.handleShellCommandResetInputMethod(this); + case "tracing": + int result = ImeTracing.getInstance().onShellCommand(this); + boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled(); + for (ClientState state : mService.mClients.values()) { + if (state != null) { + try { + state.client.setImeTraceEnabled(isImeTraceEnabled); + } catch (RemoteException e) { + Log.e(TAG, + "Error while trying to enable/disable ime " + + "trace on client window", e); + } + } + } + return result; default: getOutPrintWriter().println("Unknown command: " + imeCommand); return ShellCommandResult.FAILURE; diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index b518eb1ab6d0..a6ca25b0e6c1 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1805,5 +1805,16 @@ public final class MultiClientInputMethodManagerService { mUserDataMap.dump(fd, ipw, args); } } + + @BinderThread + @Override + public void startProtoDump(byte[] clientProtoDump) throws RemoteException { + } + + @BinderThread + @Override + public boolean isImeTraceEnabled() throws RemoteException { + return false; + } } } diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS index 25ef9facb216..c09ade9e075f 100644 --- a/services/core/java/com/android/server/inputmethod/OWNERS +++ b/services/core/java/com/android/server/inputmethod/OWNERS @@ -4,3 +4,4 @@ ogunwale@google.com yukawa@google.com tarandeep@google.com lumark@google.com +roosa@google.com diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 72734c47873e..0329c3c78e20 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -23,10 +23,10 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; 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 com.android.server.location.LocationPermissions.PERMISSION_COARSE; import static com.android.server.location.LocationPermissions.PERMISSION_FINE; -import static com.android.server.location.LocationProviderManager.FASTEST_COARSE_INTERVAL_MS; import static java.util.concurrent.TimeUnit.NANOSECONDS; @@ -36,6 +36,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.PendingIntent; +import android.app.compat.CompatChanges; import android.content.Context; import android.content.Intent; import android.location.Criteria; @@ -82,12 +83,12 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.location.LocationPermissions.PermissionLevel; import com.android.server.location.LocationRequestStatistics.PackageProviderKey; import com.android.server.location.LocationRequestStatistics.PackageStatistics; import com.android.server.location.geofence.GeofenceManager; import com.android.server.location.geofence.GeofenceProxy; import com.android.server.location.gnss.GnssManagerService; +import com.android.server.location.util.AlarmHelper; import com.android.server.location.util.AppForegroundHelper; import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; @@ -97,6 +98,7 @@ import com.android.server.location.util.LocationPowerSaveModeHelper; import com.android.server.location.util.LocationUsageLogger; import com.android.server.location.util.ScreenInteractiveHelper; import com.android.server.location.util.SettingsHelper; +import com.android.server.location.util.SystemAlarmHelper; import com.android.server.location.util.SystemAppForegroundHelper; import com.android.server.location.util.SystemAppOpsHelper; import com.android.server.location.util.SystemLocationPermissionsHelper; @@ -569,7 +571,7 @@ public class LocationManagerService extends ILocationManager.Stub { new IllegalArgumentException()); } - request = validateAndSanitizeLocationRequest(request, permissionLevel); + request = validateLocationRequest(request); LocationProviderManager manager = getLocationProviderManager(provider); Preconditions.checkArgument(manager != null, @@ -591,7 +593,7 @@ public class LocationManagerService extends ILocationManager.Stub { // clients in the system process must have an attribution tag set Preconditions.checkArgument(identity.getPid() != Process.myPid() || attributionTag != null); - request = validateAndSanitizeLocationRequest(request, permissionLevel); + request = validateLocationRequest(request); LocationProviderManager manager = getLocationProviderManager(provider); Preconditions.checkArgument(manager != null, @@ -600,8 +602,7 @@ public class LocationManagerService extends ILocationManager.Stub { manager.registerLocationRequest(request, identity, permissionLevel, pendingIntent); } - private LocationRequest validateAndSanitizeLocationRequest(LocationRequest request, - @PermissionLevel int permissionLevel) { + private LocationRequest validateLocationRequest(LocationRequest request) { WorkSource workSource = request.getWorkSource(); if (workSource != null && !workSource.isEmpty()) { mContext.enforceCallingOrSelfPermission( @@ -620,26 +621,20 @@ public class LocationManagerService extends ILocationManager.Stub { } LocationRequest.Builder sanitized = new LocationRequest.Builder(request); - if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE) != PERMISSION_GRANTED) { - sanitized.setLowPower(false); - } - if (permissionLevel < PERMISSION_FINE) { - switch (request.getQuality()) { - case LocationRequest.ACCURACY_FINE: - sanitized.setQuality(LocationRequest.ACCURACY_BLOCK); - break; - case LocationRequest.POWER_HIGH: - sanitized.setQuality(LocationRequest.POWER_LOW); - break; - } - if (request.getIntervalMillis() < FASTEST_COARSE_INTERVAL_MS) { - sanitized.setIntervalMillis(FASTEST_COARSE_INTERVAL_MS); + if (CompatChanges.isChangeEnabled(LOW_POWER_EXCEPTIONS, Binder.getCallingUid())) { + if (request.isLowPower()) { + mContext.enforceCallingOrSelfPermission( + permission.LOCATION_HARDWARE, + "low power request requires " + permission.LOCATION_HARDWARE); } - if (request.getMinUpdateIntervalMillis() < FASTEST_COARSE_INTERVAL_MS) { - sanitized.clearMinUpdateIntervalMillis(); + } else { + if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE) + != PERMISSION_GRANTED) { + sanitized.setLowPower(false); } } + if (request.getWorkSource() != null) { if (request.getWorkSource().isEmpty()) { sanitized.setWorkSource(null); @@ -716,7 +711,7 @@ public class LocationManagerService extends ILocationManager.Stub { // clients in the system process must have an attribution tag set Preconditions.checkState(identity.getPid() != Process.myPid() || attributionTag != null); - request = validateAndSanitizeLocationRequest(request, permissionLevel); + request = validateLocationRequest(request); LocationProviderManager manager = getLocationProviderManager(provider); Preconditions.checkArgument(manager != null, @@ -735,7 +730,7 @@ public class LocationManagerService extends ILocationManager.Stub { // use fine permission level to avoid creating unnecessary coarse locations Location location = gpsManager.getLastLocationUnsafe(UserHandle.USER_ALL, - PERMISSION_FINE, false); + PERMISSION_FINE, false, Long.MAX_VALUE); if (location == null) { return null; } @@ -1237,6 +1232,7 @@ public class LocationManagerService extends ILocationManager.Stub { private static class SystemInjector implements Injector { private final UserInfoHelper mUserInfoHelper; + private final AlarmHelper mAlarmHelper; private final SystemAppOpsHelper mAppOpsHelper; private final SystemLocationPermissionsHelper mLocationPermissionsHelper; private final SystemSettingsHelper mSettingsHelper; @@ -1249,6 +1245,7 @@ public class LocationManagerService extends ILocationManager.Stub { SystemInjector(Context context, UserInfoHelper userInfoHelper) { mUserInfoHelper = userInfoHelper; + mAlarmHelper = new SystemAlarmHelper(context); mAppOpsHelper = new SystemAppOpsHelper(context); mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context, mAppOpsHelper); @@ -1276,6 +1273,11 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override + public AlarmHelper getAlarmHelper() { + return mAlarmHelper; + } + + @Override public AppOpsHelper getAppOpsHelper() { return mAppOpsHelper; } diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java index 138301ae934d..cd8bf4a0154d 100644 --- a/services/core/java/com/android/server/location/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/LocationProviderManager.java @@ -16,13 +16,14 @@ package com.android.server.location; -import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; -import static android.app.AlarmManager.WINDOW_EXACT; +import static android.app.compat.CompatChanges.isChangeEnabled; +import static android.location.LocationManager.DELIVER_HISTORICAL_LOCATIONS; import static android.location.LocationManager.FUSED_PROVIDER; import static android.location.LocationManager.GPS_PROVIDER; import static android.location.LocationManager.KEY_LOCATION_CHANGED; import static android.location.LocationManager.KEY_PROVIDER_ENABLED; import static android.location.LocationManager.PASSIVE_PROVIDER; +import static android.location.LocationRequest.PASSIVE_INTERVAL; import static android.os.IPowerManager.LOCATION_MODE_NO_CHANGE; import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF; import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY; @@ -36,11 +37,11 @@ import static com.android.server.location.LocationPermissions.PERMISSION_COARSE; import static com.android.server.location.LocationPermissions.PERMISSION_FINE; import static com.android.server.location.LocationPermissions.PERMISSION_NONE; +import static java.lang.Math.max; import static java.lang.Math.min; -import static java.util.concurrent.TimeUnit.NANOSECONDS; import android.annotation.Nullable; -import android.app.AlarmManager; +import android.app.AlarmManager.OnAlarmListener; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -88,6 +89,7 @@ import com.android.server.PendingIntentUtils; import com.android.server.location.LocationPermissions.PermissionLevel; import com.android.server.location.listeners.ListenerMultiplexer; import com.android.server.location.listeners.RemoteListenerRegistration; +import com.android.server.location.util.AlarmHelper; import com.android.server.location.util.AppForegroundHelper; import com.android.server.location.util.AppForegroundHelper.AppForegroundListener; import com.android.server.location.util.AppOpsHelper; @@ -118,12 +120,12 @@ class LocationProviderManager extends LocationProviderManager.Registration, ProviderRequest> implements AbstractLocationProvider.Listener { - // fastest interval at which clients may receive coarse locations - public static final long FASTEST_COARSE_INTERVAL_MS = 10 * 60 * 1000; - private static final String WAKELOCK_TAG = "*location*"; private static final long WAKELOCK_TIMEOUT_MS = 30 * 1000; + // fastest interval at which clients may receive coarse locations + private static final long MIN_COARSE_INTERVAL_MS = 10 * 60 * 1000; + // max interval to be considered "high power" request private static final long MAX_HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000; @@ -133,8 +135,15 @@ class LocationProviderManager extends // max timeout allowed for getting the current location private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30 * 1000; - // max jitter allowed for fastest interval evaluation - private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 100; + // max jitter allowed for min update interval as a percentage of the interval + private static final float FASTEST_INTERVAL_JITTER_PERCENTAGE = .10f; + + // max absolute jitter allowed for min update interval evaluation + private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 5 * 1000; + + // minimum amount of request delay in order to respect the delay, below this value the request + // will just be scheduled immediately + private static final long MIN_REQUEST_DELAY_MS = 30 * 1000; protected interface LocationTransport { @@ -221,6 +230,7 @@ class LocationProviderManager extends /** * Must be implemented to return the location this operation intends to deliver. */ + @Nullable Location getLocation(); } @@ -312,7 +322,7 @@ class LocationProviderManager extends mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey()); } onHighPowerUsageChanged(); - return null; + return onProviderListenerActive(); } @Override @@ -325,6 +335,22 @@ class LocationProviderManager extends if (!getRequest().isHiddenFromAppOps()) { mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey()); } + return onProviderListenerInactive(); + } + + /** + * Subclasses may override this instead of {@link #onActive()}. + */ + @GuardedBy("mLock") + protected LocationListenerOperation onProviderListenerActive() { + return null; + } + + /** + * Subclasses may override this instead of {@link #onInactive()} ()}. + */ + @GuardedBy("mLock") + protected LocationListenerOperation onProviderListenerInactive() { return null; } @@ -333,6 +359,14 @@ class LocationProviderManager extends return mProviderLocationRequest; } + @GuardedBy("mLock") + final void initializeLastLocation(@Nullable Location location) { + if (mLastLocation == null) { + mLastLocation = location; + } + } + + @GuardedBy("mLock") public final Location getLastDeliveredLocation() { return mLastLocation; } @@ -465,9 +499,27 @@ class LocationProviderManager extends } private LocationRequest calculateProviderLocationRequest() { - LocationRequest.Builder builder = new LocationRequest.Builder(super.getRequest()); + LocationRequest baseRequest = super.getRequest(); + LocationRequest.Builder builder = new LocationRequest.Builder(baseRequest); - if (super.getRequest().isLocationSettingsIgnored()) { + if (mPermissionLevel < PERMISSION_FINE) { + switch (baseRequest.getQuality()) { + case LocationRequest.ACCURACY_FINE: + builder.setQuality(LocationRequest.ACCURACY_BLOCK); + break; + case LocationRequest.POWER_HIGH: + builder.setQuality(LocationRequest.POWER_LOW); + break; + } + if (baseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) { + builder.setIntervalMillis(MIN_COARSE_INTERVAL_MS); + } + if (baseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) { + builder.clearMinUpdateIntervalMillis(); + } + } + + if (baseRequest.isLocationSettingsIgnored()) { // if we are not currently allowed use location settings ignored, disable it if (!mSettingsHelper.getIgnoreSettingsPackageWhitelist().contains( getIdentity().getPackageName()) && !mLocationManagerInternal.isProvider( @@ -476,10 +528,10 @@ class LocationProviderManager extends } } - if (!super.getRequest().isLocationSettingsIgnored() && !isThrottlingExempt()) { + if (!baseRequest.isLocationSettingsIgnored() && !isThrottlingExempt()) { // throttle in the background if (!mForeground) { - builder.setIntervalMillis(Math.max(super.getRequest().getIntervalMillis(), + builder.setIntervalMillis(max(baseRequest.getIntervalMillis(), mSettingsHelper.getBackgroundThrottleIntervalMs())); } } @@ -534,7 +586,7 @@ class LocationProviderManager extends } protected abstract class LocationRegistration extends Registration implements - AlarmManager.OnAlarmListener, ProviderEnabledListener { + OnAlarmListener, ProviderEnabledListener { private final PowerManager.WakeLock mWakeLock; @@ -561,17 +613,15 @@ class LocationProviderManager extends @GuardedBy("mLock") @Override protected final void onProviderListenerRegister() { - mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs( - SystemClock.elapsedRealtime()); + long registerTimeMs = SystemClock.elapsedRealtime(); + mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs); // add alarm for expiration - if (mExpirationRealtimeMs < SystemClock.elapsedRealtime()) { - remove(); + if (mExpirationRealtimeMs <= registerTimeMs) { + onAlarm(); } else if (mExpirationRealtimeMs < Long.MAX_VALUE) { - AlarmManager alarmManager = Objects.requireNonNull( - mContext.getSystemService(AlarmManager.class)); - alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT, - 0, this, FgThread.getHandler(), getWorkSource()); + mAlarmHelper.setDelayedAlarm(mExpirationRealtimeMs - registerTimeMs, this, + getWorkSource()); } // start listening for provider enabled/disabled events @@ -594,9 +644,7 @@ class LocationProviderManager extends // remove alarm for expiration if (mExpirationRealtimeMs < Long.MAX_VALUE) { - AlarmManager alarmManager = Objects.requireNonNull( - mContext.getSystemService(AlarmManager.class)); - alarmManager.cancel(this); + mAlarmHelper.cancel(this); } onLocationListenerUnregister(); @@ -614,6 +662,39 @@ class LocationProviderManager extends @GuardedBy("mLock") protected void onLocationListenerUnregister() {} + @GuardedBy("mLock") + @Override + protected final LocationListenerOperation onProviderListenerActive() { + // a new registration may not get a location immediately, the provider request may be + // delayed. therefore we deliver a historical location if available. since delivering an + // older location could be considered a breaking change for some applications, we only + // do so for apps targeting S+. + if (isChangeEnabled(DELIVER_HISTORICAL_LOCATIONS, getIdentity().getUid())) { + long maxLocationAgeMs = getRequest().getIntervalMillis(); + Location lastDeliveredLocation = getLastDeliveredLocation(); + if (lastDeliveredLocation != null) { + // ensure that location is fresher than the last delivered location + maxLocationAgeMs = min(maxLocationAgeMs, + lastDeliveredLocation.getElapsedRealtimeAgeMillis() - 1); + } + + // requests are never delayed less than MIN_REQUEST_DELAY_MS, so it only makes sense + // to deliver historical locations to clients with a last location older than that + if (maxLocationAgeMs > MIN_REQUEST_DELAY_MS) { + Location lastLocation = getLastLocationUnsafe( + getIdentity().getUserId(), + PERMISSION_FINE, // acceptLocationChange() handles coarsening this + getRequest().isLocationSettingsIgnored(), + maxLocationAgeMs); + if (lastLocation != null) { + return acceptLocationChange(lastLocation); + } + } + } + + return null; + } + @Override public void onAlarm() { if (D) { @@ -624,6 +705,8 @@ class LocationProviderManager extends synchronized (mLock) { remove(); + // no need to remove alarm after it's fired + mExpirationRealtimeMs = Long.MAX_VALUE; } } @@ -658,11 +741,11 @@ class LocationProviderManager extends Location lastDeliveredLocation = getLastDeliveredLocation(); if (lastDeliveredLocation != null) { // check fastest interval - long deltaMs = NANOSECONDS.toMillis( - location.getElapsedRealtimeNanos() - - lastDeliveredLocation.getElapsedRealtimeNanos()); - if (deltaMs < getRequest().getMinUpdateIntervalMillis() - - MAX_FASTEST_INTERVAL_JITTER_MS) { + long deltaMs = location.getElapsedRealtimeMillis() + - lastDeliveredLocation.getElapsedRealtimeMillis(); + long maxJitterMs = min((long) (FASTEST_INTERVAL_JITTER_PERCENTAGE + * getRequest().getIntervalMillis()), MAX_FASTEST_INTERVAL_JITTER_MS); + if (deltaMs < getRequest().getMinUpdateIntervalMillis() - maxJitterMs) { return null; } @@ -871,7 +954,7 @@ class LocationProviderManager extends } protected final class GetCurrentLocationListenerRegistration extends Registration implements - IBinder.DeathRecipient, ProviderEnabledListener, AlarmManager.OnAlarmListener { + IBinder.DeathRecipient, ProviderEnabledListener, OnAlarmListener { private volatile LocationTransport mTransport; @@ -902,15 +985,15 @@ class LocationProviderManager extends remove(); } - mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs( - SystemClock.elapsedRealtime()); + long registerTimeMs = SystemClock.elapsedRealtime(); + mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs); // add alarm for expiration - if (mExpirationRealtimeMs < Long.MAX_VALUE) { - AlarmManager alarmManager = Objects.requireNonNull( - mContext.getSystemService(AlarmManager.class)); - alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT, - 0, this, FgThread.getHandler(), getWorkSource()); + if (mExpirationRealtimeMs <= registerTimeMs) { + onAlarm(); + } else if (mExpirationRealtimeMs < Long.MAX_VALUE) { + mAlarmHelper.setDelayedAlarm(mExpirationRealtimeMs - registerTimeMs, this, + getWorkSource()); } // if this request is ignoring location settings, then we don't want to immediately fail @@ -935,9 +1018,7 @@ class LocationProviderManager extends // remove alarm for expiration if (mExpirationRealtimeMs < Long.MAX_VALUE) { - AlarmManager alarmManager = Objects.requireNonNull( - mContext.getSystemService(AlarmManager.class)); - alarmManager.cancel(this); + mAlarmHelper.cancel(this); } ((IBinder) getKey()).unlinkToDeath(this, 0); @@ -953,6 +1034,8 @@ class LocationProviderManager extends synchronized (mLock) { deliverLocation(null); + // no need to remove alarm after it's fired + mExpirationRealtimeMs = Long.MAX_VALUE; } } @@ -964,6 +1047,12 @@ class LocationProviderManager extends Preconditions.checkState(Thread.holdsLock(mLock)); } + // check expiration time - alarm is not guaranteed to go off at the right time, + // especially for short intervals + if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) { + fineLocation = null; + } + // lastly - note app ops Location location; if (fineLocation == null) { @@ -1077,6 +1166,7 @@ class LocationProviderManager extends protected final LocationManagerInternal mLocationManagerInternal; protected final SettingsHelper mSettingsHelper; protected final UserInfoHelper mUserInfoHelper; + protected final AlarmHelper mAlarmHelper; protected final AppOpsHelper mAppOpsHelper; protected final LocationPermissionsHelper mLocationPermissionsHelper; protected final AppForegroundHelper mAppForegroundHelper; @@ -1120,6 +1210,9 @@ class LocationProviderManager extends // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary protected final MockableLocationProvider mProvider; + @GuardedBy("mLock") + @Nullable private OnAlarmListener mDelayedRegister; + LocationProviderManager(Context context, Injector injector, String name, @Nullable PassiveLocationProviderManager passiveManager) { mContext = context; @@ -1135,6 +1228,7 @@ class LocationProviderManager extends LocalServices.getService(LocationManagerInternal.class)); mSettingsHelper = injector.getSettingsHelper(); mUserInfoHelper = injector.getUserInfoHelper(); + mAlarmHelper = injector.getAlarmHelper(); mAppOpsHelper = injector.getAppOpsHelper(); mLocationPermissionsHelper = injector.getLocationPermissionsHelper(); mAppForegroundHelper = injector.getAppForegroundHelper(); @@ -1344,7 +1438,7 @@ class LocationProviderManager extends } Location location = getLastLocationUnsafe(identity.getUserId(), permissionLevel, - ignoreLocationSettings); + ignoreLocationSettings, Long.MAX_VALUE); // we don't note op here because we don't know what the client intends to do with the // location, the client is responsible for noting if necessary @@ -1364,13 +1458,14 @@ class LocationProviderManager extends */ @Nullable public Location getLastLocationUnsafe(int userId, @PermissionLevel int permissionLevel, - boolean ignoreLocationSettings) { + boolean ignoreLocationSettings, long maximumAgeMs) { if (userId == UserHandle.USER_ALL) { + // find the most recent location across all users Location lastLocation = null; final int[] runningUserIds = mUserInfoHelper.getRunningUserIds(); for (int i = 0; i < runningUserIds.length; i++) { Location next = getLastLocationUnsafe(runningUserIds[i], permissionLevel, - ignoreLocationSettings); + ignoreLocationSettings, maximumAgeMs); if (lastLocation == null || (next != null && next.getElapsedRealtimeNanos() > lastLocation.getElapsedRealtimeNanos())) { lastLocation = next; @@ -1381,18 +1476,30 @@ class LocationProviderManager extends Preconditions.checkArgument(userId >= 0); + Location location; synchronized (mLock) { LastLocation lastLocation = mLastLocations.get(userId); if (lastLocation == null) { - return null; + location = null; + } else { + location = lastLocation.get(permissionLevel, ignoreLocationSettings); } - return lastLocation.get(permissionLevel, ignoreLocationSettings); } + + if (location == null) { + return null; + } + + if (location.getElapsedRealtimeAgeMillis() > maximumAgeMs) { + return null; + } + + return location; } public void injectLastLocation(Location location, int userId) { synchronized (mLock) { - if (getLastLocationUnsafe(userId, PERMISSION_FINE, false) == null) { + if (getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE) == null) { setLastLocation(location, userId); } } @@ -1455,22 +1562,14 @@ class LocationProviderManager extends return null; } - Location lastLocation = getLastLocationUnsafe(callerIdentity.getUserId(), - permissionLevel, request.isLocationSettingsIgnored()); + Location lastLocation = getLastLocationUnsafe( + callerIdentity.getUserId(), + permissionLevel, + request.isLocationSettingsIgnored(), + MAX_CURRENT_LOCATION_AGE_MS); if (lastLocation != null) { - long locationAgeMs = NANOSECONDS.toMillis( - SystemClock.elapsedRealtimeNanos() - - lastLocation.getElapsedRealtimeNanos()); - if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) { - registration.deliverLocation(lastLocation); - return null; - } - - if (!mAppForegroundHelper.isAppForeground(Binder.getCallingUid()) - && locationAgeMs < mSettingsHelper.getBackgroundThrottleIntervalMs()) { - registration.deliverLocation(null); - return null; - } + registration.deliverLocation(lastLocation); + return null; } // if last location isn't good enough then we add a location request @@ -1629,6 +1728,16 @@ class LocationProviderManager extends @GuardedBy("mLock") @Override + protected void onRegistrationReplaced(Object key, Registration oldRegistration, + Registration newRegistration) { + // by saving the last delivered location state we are able to potentially delay the + // resulting provider request longer and save additional power + newRegistration.initializeLastLocation(oldRegistration.getLastDeliveredLocation()); + super.onRegistrationReplaced(key, oldRegistration, newRegistration); + } + + @GuardedBy("mLock") + @Override protected void onRegistrationRemoved(Object key, Registration registration) { if (Build.IS_DEBUGGABLE) { Preconditions.checkState(Thread.holdsLock(mLock)); @@ -1652,21 +1761,61 @@ class LocationProviderManager extends @GuardedBy("mLock") @Override - protected boolean registerWithService(ProviderRequest mergedRequest, + protected boolean registerWithService(ProviderRequest request, Collection<Registration> registrations) { - if (Build.IS_DEBUGGABLE) { - Preconditions.checkState(Thread.holdsLock(mLock)); - } - - mProvider.setRequest(mergedRequest); - return true; + return reregisterWithService(EMPTY_REQUEST, request, registrations); } @GuardedBy("mLock") @Override protected boolean reregisterWithService(ProviderRequest oldRequest, ProviderRequest newRequest, Collection<Registration> registrations) { - return registerWithService(newRequest, registrations); + if (Build.IS_DEBUGGABLE) { + Preconditions.checkState(Thread.holdsLock(mLock)); + } + + if (mDelayedRegister != null) { + mAlarmHelper.cancel(mDelayedRegister); + mDelayedRegister = null; + } + + // calculate how long the new request should be delayed before sending it off to the + // provider, under the assumption that once we send the request off, the provider will + // immediately attempt to deliver a new location satisfying that request. + long delayMs; + if (!oldRequest.isLocationSettingsIgnored() && newRequest.isLocationSettingsIgnored()) { + delayMs = 0; + } else if (newRequest.getIntervalMillis() > oldRequest.getIntervalMillis()) { + // if the interval has increased, tell the provider immediately, so it can save power + // (even though technically this could burn extra power in the short term by producing + // an extra location - the provider itself is free to detect an increasing interval and + // delay its own location) + delayMs = 0; + } else { + delayMs = calculateRequestDelayMillis(newRequest.getIntervalMillis(), registrations); + } + + // the delay should never exceed the new interval + Preconditions.checkState(delayMs >= 0 && delayMs <= newRequest.getIntervalMillis()); + + if (delayMs < MIN_REQUEST_DELAY_MS) { + mProvider.setRequest(newRequest); + } else { + mDelayedRegister = new OnAlarmListener() { + @Override + public void onAlarm() { + synchronized (mLock) { + if (mDelayedRegister == this) { + mProvider.setRequest(newRequest); + mDelayedRegister = null; + } + } + } + }; + mAlarmHelper.setDelayedAlarm(delayMs, mDelayedRegister, newRequest.getWorkSource()); + } + + return true; } @GuardedBy("mLock") @@ -1733,42 +1882,40 @@ class LocationProviderManager extends Preconditions.checkState(Thread.holdsLock(mLock)); } - ArrayList<Registration> providerRegistrations = new ArrayList<>(registrations.size()); - long intervalMs = Long.MAX_VALUE; boolean locationSettingsIgnored = false; boolean lowPower = true; ArrayList<LocationRequest> locationRequests = new ArrayList<>(registrations.size()); + for (Registration registration : registrations) { - LocationRequest locationRequest = registration.getRequest(); + LocationRequest request = registration.getRequest(); - // passive requests do not contribute to the provider - if (locationRequest.getIntervalMillis() == LocationRequest.PASSIVE_INTERVAL) { + // passive requests do not contribute to the provider request + if (request.getIntervalMillis() == PASSIVE_INTERVAL) { continue; } - providerRegistrations.add(registration); - intervalMs = min(locationRequest.getIntervalMillis(), intervalMs); - locationSettingsIgnored |= locationRequest.isLocationSettingsIgnored(); - lowPower &= locationRequest.isLowPower(); - locationRequests.add(locationRequest); + intervalMs = min(request.getIntervalMillis(), intervalMs); + locationSettingsIgnored |= request.isLocationSettingsIgnored(); + lowPower &= request.isLowPower(); + locationRequests.add(request); } // calculate who to blame for power in a somewhat arbitrary fashion. we pick a threshold // interval slightly higher that the minimum interval, and spread the blame across all // contributing registrations under that threshold (since worksource does not allow us to // represent differing power blame ratios). - WorkSource workSource = new WorkSource(); long thresholdIntervalMs = (intervalMs + 1000) * 3 / 2; - if (thresholdIntervalMs < 0) { - // handle overflow by setting to one below the passive interval - thresholdIntervalMs = Long.MAX_VALUE - 1; + if (thresholdIntervalMs < 0 || thresholdIntervalMs >= PASSIVE_INTERVAL) { + // check for and handle overflow by setting to one below the passive interval so passive + // requests are automatically skipped + thresholdIntervalMs = PASSIVE_INTERVAL - 1; } - final int providerRegistrationsSize = providerRegistrations.size(); - for (int i = 0; i < providerRegistrationsSize; i++) { - Registration registration = providerRegistrations.get(i); + + WorkSource workSource = new WorkSource(); + for (Registration registration : registrations) { if (registration.getRequest().getIntervalMillis() <= thresholdIntervalMs) { - workSource.add(providerRegistrations.get(i).getWorkSource()); + workSource.add(registration.getWorkSource()); } } @@ -1781,6 +1928,47 @@ class LocationProviderManager extends .build(); } + @GuardedBy("mLock") + protected long calculateRequestDelayMillis(long newIntervalMs, + Collection<Registration> registrations) { + // calculate the minimum delay across all registrations, ensuring that it is not more than + // the requested interval + long delayMs = newIntervalMs; + for (Registration registration : registrations) { + if (delayMs == 0) { + break; + } + + LocationRequest locationRequest = registration.getRequest(); + Location last = registration.getLastDeliveredLocation(); + + if (last == null && !locationRequest.isLocationSettingsIgnored()) { + // if this request has never gotten any location and it's not ignoring location + // settings, then we pretend that this request has gotten the last applicable cached + // location for our calculations instead. this prevents spammy add/remove behavior + last = getLastLocationUnsafe( + registration.getIdentity().getUserId(), + PERMISSION_FINE, + false, + locationRequest.getIntervalMillis()); + } + + long registrationDelayMs; + if (last == null) { + // if this request has never gotten any location then there's no delay + registrationDelayMs = 0; + } else { + // otherwise the delay is the amount of time until the next location is expected + registrationDelayMs = max(0, + locationRequest.getIntervalMillis() - last.getElapsedRealtimeAgeMillis()); + } + + delayMs = min(delayMs, registrationDelayMs); + } + + return delayMs; + } + private void onUserChanged(int userId, int change) { synchronized (mLock) { switch (change) { @@ -2068,7 +2256,7 @@ class LocationProviderManager extends ipw.increaseIndent(); } ipw.print("last location="); - ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false)); + ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE)); ipw.print("enabled="); ipw.println(isEnabled(userId)); if (userIds.length != 1) { @@ -2126,24 +2314,37 @@ class LocationProviderManager extends } } - public void set(Location location, Location coarseLocation) { - mFineLocation = location; + public void set(Location fineLocation, Location coarseLocation) { + mFineLocation = calculateNextFine(mFineLocation, fineLocation); mCoarseLocation = calculateNextCoarse(mCoarseLocation, coarseLocation); } - public void setBypass(Location location, Location coarseLocation) { - mFineBypassLocation = location; + public void setBypass(Location fineLocation, Location coarseLocation) { + mFineBypassLocation = calculateNextFine(mFineBypassLocation, fineLocation); mCoarseBypassLocation = calculateNextCoarse(mCoarseBypassLocation, coarseLocation); } + private Location calculateNextFine(@Nullable Location oldFine, Location newFine) { + if (oldFine == null) { + return newFine; + } + + // update last fine interval only if more recent + if (newFine.getElapsedRealtimeNanos() > oldFine.getElapsedRealtimeNanos()) { + return newFine; + } else { + return oldFine; + } + } + private Location calculateNextCoarse(@Nullable Location oldCoarse, Location newCoarse) { if (oldCoarse == null) { return newCoarse; } + // update last coarse interval only if enough time has passed - long timeDeltaMs = NANOSECONDS.toMillis(newCoarse.getElapsedRealtimeNanos()) - - NANOSECONDS.toMillis(oldCoarse.getElapsedRealtimeNanos()); - if (timeDeltaMs > FASTEST_COARSE_INTERVAL_MS) { + if (newCoarse.getElapsedRealtimeNanos() - MIN_COARSE_INTERVAL_MS + > oldCoarse.getElapsedRealtimeNanos()) { return newCoarse; } else { return oldCoarse; diff --git a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java index afeb6444c40b..2870d41e5248 100644 --- a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java +++ b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java @@ -20,14 +20,12 @@ import android.annotation.Nullable; import android.content.Context; import android.location.Location; import android.location.LocationManager; -import android.location.LocationRequest; import android.os.Binder; import com.android.internal.location.ProviderRequest; import com.android.internal.util.Preconditions; import com.android.server.location.util.Injector; -import java.util.ArrayList; import java.util.Collection; class PassiveLocationProviderManager extends LocationProviderManager { @@ -65,17 +63,20 @@ class PassiveLocationProviderManager extends LocationProviderManager { @Override protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) { - ProviderRequest.Builder providerRequest = new ProviderRequest.Builder() - .setIntervalMillis(0); - - ArrayList<LocationRequest> requests = new ArrayList<>(registrations.size()); + boolean locationSettingsIgnored = false; for (Registration registration : registrations) { - requests.add(registration.getRequest()); - if (registration.getRequest().isLocationSettingsIgnored()) { - providerRequest.setLocationSettingsIgnored(true); - } + locationSettingsIgnored |= registration.getRequest().isLocationSettingsIgnored(); } - return providerRequest.setLocationRequests(requests).build(); + return new ProviderRequest.Builder() + .setIntervalMillis(0) + .setLocationSettingsIgnored(locationSettingsIgnored) + .build(); + } + + @Override + protected long calculateRequestDelayMillis(long newIntervalMs, + Collection<Registration> registrations) { + return 0; } } diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java index 87d668a07d70..0cda57cf1936 100644 --- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java @@ -57,6 +57,8 @@ import java.util.function.Predicate; * <li>{@link #onRegister()}</li> * <li>{@link ListenerRegistration#onRegister(Object)}</li> * <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}</li> + * <li>{@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} (only + * invoked if this registration is replacing a prior registration)</li> * <li>{@link #onActive()}</li> * <li>{@link ListenerRegistration#onActive()}</li> * <li>{@link ListenerRegistration#onInactive()}</li> @@ -183,6 +185,17 @@ public abstract class ListenerMultiplexer<TKey, TListener, protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {} /** + * Invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)} if a + * registration is replacing an old registration. The old registration will have already been + * unregistered. Invoked while holding the multiplexer's internal lock. The default behavior is + * simply to call into {@link #onRegistrationAdded(Object, ListenerRegistration)}. + */ + protected void onRegistrationReplaced(@NonNull TKey key, @NonNull TRegistration oldRegistration, + @NonNull TRegistration newRegistration) { + onRegistrationAdded(key, newRegistration); + } + + /** * Invoked when a registration is removed. Invoked while holding the multiplexer's internal * lock. */ @@ -227,9 +240,10 @@ public abstract class ListenerMultiplexer<TKey, TListener, boolean wasEmpty = mRegistrations.isEmpty(); + TRegistration oldRegistration = null; int index = mRegistrations.indexOfKey(key); if (index >= 0) { - removeRegistration(index, false); + oldRegistration = removeRegistration(index, false); mRegistrations.setValueAt(index, registration); } else { mRegistrations.put(key, registration); @@ -239,7 +253,11 @@ public abstract class ListenerMultiplexer<TKey, TListener, onRegister(); } registration.onRegister(key); - onRegistrationAdded(key, registration); + if (oldRegistration == null) { + onRegistrationAdded(key, registration); + } else { + onRegistrationReplaced(key, oldRegistration, registration); + } onRegistrationActiveChanged(registration); } } @@ -320,7 +338,7 @@ public abstract class ListenerMultiplexer<TKey, TListener, } @GuardedBy("mRegistrations") - private void removeRegistration(int index, boolean removeEntry) { + private TRegistration removeRegistration(int index, boolean removeEntry) { if (Build.IS_DEBUGGABLE) { Preconditions.checkState(Thread.holdsLock(mRegistrations)); } @@ -347,6 +365,8 @@ public abstract class ListenerMultiplexer<TKey, TListener, } } } + + return registration; } /** diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java index e28d73e8e0b9..5dc4318e5803 100644 --- a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java @@ -18,7 +18,9 @@ package com.android.server.location.timezone; import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; import android.annotation.NonNull; @@ -78,16 +80,19 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider { synchronized (mSharedLock) { ProviderState currentState = mCurrentState.get(); switch (currentState.stateEnum) { - case PROVIDER_STATE_ENABLED: { + case PROVIDER_STATE_ENABLED_INITIALIZING: + case PROVIDER_STATE_ENABLED_UNCERTAIN: + case PROVIDER_STATE_ENABLED_CERTAIN: { // Losing a remote provider is treated as becoming uncertain. String msg = "handleProviderLost reason=" + reason + ", mProviderName=" + mProviderName + ", currentState=" + currentState; debugLog(msg); - // This is an unusual PROVIDER_STATE_ENABLED state because event == null + // This is an unusual PROVIDER_STATE_ENABLED_UNCERTAIN state because + // event == null ProviderState newState = currentState.newState( - PROVIDER_STATE_ENABLED, null, currentState.currentUserConfiguration, - msg); + PROVIDER_STATE_ENABLED_UNCERTAIN, null, + currentState.currentUserConfiguration, msg); setCurrentState(newState, true); break; } @@ -118,7 +123,9 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider { synchronized (mSharedLock) { ProviderState currentState = mCurrentState.get(); switch (currentState.stateEnum) { - case PROVIDER_STATE_ENABLED: { + case PROVIDER_STATE_ENABLED_INITIALIZING: + case PROVIDER_STATE_ENABLED_CERTAIN: + case PROVIDER_STATE_ENABLED_UNCERTAIN: { debugLog("handleOnProviderBound mProviderName=" + mProviderName + ", currentState=" + currentState + ": Provider is enabled."); break; 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 bedaedacf0e3..179ce750d106 100644 --- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java +++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java @@ -24,7 +24,9 @@ import static com.android.server.location.timezone.LocationTimeZoneManagerServic import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; import android.annotation.NonNull; @@ -38,6 +40,7 @@ import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; import java.time.Duration; +import java.util.List; import java.util.Objects; /** @@ -49,8 +52,7 @@ import java.util.Objects; */ class ControllerImpl extends LocationTimeZoneProviderController { - @NonNull private final LocationTimeZoneProvider mProvider; - @NonNull private final SingleRunnableQueue mDelayedSuggestionQueue; + @NonNull private final LocationTimeZoneProvider mPrimaryProvider; @GuardedBy("mSharedLock") // Non-null after initialize() @@ -65,12 +67,9 @@ class ControllerImpl extends LocationTimeZoneProviderController { private Callback mCallback; /** - * Contains any currently pending suggestion on {@link #mDelayedSuggestionQueue}, if there is - * one. + * Used for scheduling uncertainty timeouts, i.e after the provider has reported uncertainty. */ - @GuardedBy("mSharedLock") - @Nullable - private GeolocationTimeZoneSuggestion mPendingSuggestion; + @NonNull private final SingleRunnableQueue mUncertaintyTimeoutQueue; /** Contains the last suggestion actually made, if there is one. */ @GuardedBy("mSharedLock") @@ -78,10 +77,10 @@ class ControllerImpl extends LocationTimeZoneProviderController { private GeolocationTimeZoneSuggestion mLastSuggestion; ControllerImpl(@NonNull ThreadingDomain threadingDomain, - @NonNull LocationTimeZoneProvider provider) { + @NonNull LocationTimeZoneProvider primaryProvider) { super(threadingDomain); - mDelayedSuggestionQueue = threadingDomain.createSingleRunnableQueue(); - mProvider = Objects.requireNonNull(provider); + mUncertaintyTimeoutQueue = threadingDomain.createSingleRunnableQueue(); + mPrimaryProvider = Objects.requireNonNull(primaryProvider); } @Override @@ -94,8 +93,12 @@ class ControllerImpl extends LocationTimeZoneProviderController { mCallback = Objects.requireNonNull(callback); mCurrentUserConfiguration = environment.getCurrentUserConfigurationInternal(); - mProvider.initialize(ControllerImpl.this::onProviderStateChange); - enableOrDisableProvider(mCurrentUserConfiguration); + LocationTimeZoneProvider.ProviderListener providerListener = + ControllerImpl.this::onProviderStateChange; + mPrimaryProvider.initialize(providerListener); + + alterProviderEnabledStateIfRequired( + null /* oldConfiguration */, mCurrentUserConfiguration); } } @@ -115,92 +118,148 @@ class ControllerImpl extends LocationTimeZoneProviderController { // If the user changed, disable the provider if needed. It may be re-enabled for // the new user below if their settings allow. debugLog("User changed. old=" + oldConfig.getUserId() - + ", new=" + newConfig.getUserId()); - debugLog("Disabling LocationTimeZoneProviders as needed"); - if (mProvider.getCurrentState().stateEnum == PROVIDER_STATE_ENABLED) { - mProvider.disable(); - } - } + + ", new=" + newConfig.getUserId() + ": Disabling provider"); + disableProvider(); - enableOrDisableProvider(newConfig); + alterProviderEnabledStateIfRequired(null /* oldConfiguration */, newConfig); + } else { + alterProviderEnabledStateIfRequired(oldConfig, newConfig); + } } } } + @Override + boolean isUncertaintyTimeoutSet() { + return mUncertaintyTimeoutQueue.hasQueued(); + } + + @Override + long getUncertaintyTimeoutDelayMillis() { + return mUncertaintyTimeoutQueue.getQueuedDelayMillis(); + } + @GuardedBy("mSharedLock") - private void enableOrDisableProvider(@NonNull ConfigurationInternal configuration) { - ProviderState providerState = mProvider.getCurrentState(); - boolean geoDetectionEnabled = configuration.getGeoDetectionEnabledBehavior(); - boolean providerWasEnabled = providerState.stateEnum == PROVIDER_STATE_ENABLED; - if (geoDetectionEnabled) { - switch (providerState.stateEnum) { - case PROVIDER_STATE_DISABLED: { - debugLog("Enabling " + mProvider); - mProvider.enable( - configuration, mEnvironment.getProviderInitializationTimeout()); - break; - } - case PROVIDER_STATE_ENABLED: { - debugLog("No need to enable " + mProvider + ": already enabled"); - break; - } - case PROVIDER_STATE_PERM_FAILED: { - debugLog("Unable to enable " + mProvider + ": it is perm failed"); - break; - } - default: - warnLog("Unknown provider state: " + mProvider); - break; + private void disableProvider() { + disableProviderIfEnabled(mPrimaryProvider); + + // By definition, if the provider is disabled, the controller is uncertain. + cancelUncertaintyTimeout(); + } + + @GuardedBy("mSharedLock") + private void disableProviderIfEnabled(LocationTimeZoneProvider provider) { + if (provider.getCurrentState().isEnabled()) { + disableProvider(provider); + } + } + + @GuardedBy("mSharedLock") + private void disableProvider(LocationTimeZoneProvider provider) { + ProviderState providerState = provider.getCurrentState(); + switch (providerState.stateEnum) { + case PROVIDER_STATE_DISABLED: { + debugLog("No need to disable " + provider + ": already disabled"); + break; } - } else { - switch (providerState.stateEnum) { - case PROVIDER_STATE_DISABLED: { - debugLog("No need to disable " + mProvider + ": already enabled"); - break; - } - case PROVIDER_STATE_ENABLED: { - debugLog("Disabling " + mProvider); - mProvider.disable(); - break; - } - case PROVIDER_STATE_PERM_FAILED: { - debugLog("Unable to disable " + mProvider + ": it is perm failed"); - break; - } - default: { - warnLog("Unknown provider state: " + mProvider); - break; - } + case PROVIDER_STATE_ENABLED_INITIALIZING: + case PROVIDER_STATE_ENABLED_CERTAIN: + case PROVIDER_STATE_ENABLED_UNCERTAIN: { + debugLog("Disabling " + provider); + provider.disable(); + break; } + case PROVIDER_STATE_PERM_FAILED: { + debugLog("Unable to disable " + provider + ": it is perm failed"); + break; + } + default: { + warnLog("Unknown provider state: " + provider); + break; + } + } + } + + /** + * Sets the provider into the correct enabled/disabled state for the {@code newConfiguration} + * and, if there is a provider state change, makes any suggestions required to inform the + * downstream time zone detection code. + * + * <p>This is a utility method that exists to avoid duplicated logic for the various cases when + * provider enabled / disabled state may need to be set or changed, e.g. during initialization + * or when a new configuration has been received. + */ + @GuardedBy("mSharedLock") + private void alterProviderEnabledStateIfRequired( + @Nullable ConfigurationInternal oldConfiguration, + @NonNull ConfigurationInternal newConfiguration) { + + // Provider enabled / disabled states only need to be changed if geoDetectionEnabled has + // changed. + boolean oldGeoDetectionEnabled = oldConfiguration != null + && oldConfiguration.getGeoDetectionEnabledBehavior(); + boolean newGeoDetectionEnabled = newConfiguration.getGeoDetectionEnabledBehavior(); + if (oldGeoDetectionEnabled == newGeoDetectionEnabled) { + return; } - boolean isProviderEnabled = - mProvider.getCurrentState().stateEnum == PROVIDER_STATE_ENABLED; - - if (isProviderEnabled) { - if (!providerWasEnabled) { - // When a provider has first been enabled, we allow it some time for it to - // initialize before sending its first event. - Duration initializationTimeout = mEnvironment.getProviderInitializationTimeout() - .plus(mEnvironment.getProviderInitializationTimeoutFuzz()); - // This sets up an empty suggestion to trigger if no explicit "certain" or - // "uncertain" suggestion preempts it within initializationTimeout. If, for some - // reason, the provider does not produce any events then this scheduled suggestion - // will ensure the controller makes at least an "uncertain" suggestion. - suggestDelayed(createEmptySuggestion("No event received from provider in" - + " initializationTimeout=" + initializationTimeout), - initializationTimeout); + if (newGeoDetectionEnabled) { + // Try to enable the primary provider. + tryEnableProvider(mPrimaryProvider, newConfiguration); + + ProviderState newPrimaryState = mPrimaryProvider.getCurrentState(); + if (!newPrimaryState.isEnabled()) { + // If the provider is perm failed then the controller is immediately considered + // uncertain. + GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( + "Provider is failed:" + + " primary=" + mPrimaryProvider.getCurrentState()); + makeSuggestion(suggestion); } } else { - // Clear any queued suggestions. - clearDelayedSuggestion(); + disableProvider(); + + // There can be an uncertainty timeout set if the controller most recently received + // an uncertain event. This is a no-op if there isn't a timeout set. + cancelUncertaintyTimeout(); - // If the provider is now not enabled, and a previous "certain" suggestion has been - // made, then a new "uncertain" suggestion must be made to indicate the provider no - // longer has an opinion and will not be sending updates. + // If a previous "certain" suggestion has been made, then a new "uncertain" + // suggestion must now be made to indicate the controller {does not / no longer has} + // an opinion and will not be sending further updates (until at least the config + // changes again and providers are re-enabled). if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) { - suggestImmediate(createEmptySuggestion( - "Provider disabled, clearing previous suggestion")); + GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( + "Provider is disabled:" + + " primary=" + mPrimaryProvider.getCurrentState()); + makeSuggestion(suggestion); + } + } + } + + private void tryEnableProvider(@NonNull LocationTimeZoneProvider provider, + @NonNull ConfigurationInternal configuration) { + ProviderState providerState = provider.getCurrentState(); + switch (providerState.stateEnum) { + case PROVIDER_STATE_DISABLED: { + debugLog("Enabling " + provider); + provider.enable(configuration, mEnvironment.getProviderInitializationTimeout(), + mEnvironment.getProviderInitializationTimeoutFuzz()); + break; + } + case PROVIDER_STATE_ENABLED_INITIALIZING: + case PROVIDER_STATE_ENABLED_CERTAIN: + case PROVIDER_STATE_ENABLED_UNCERTAIN: { + debugLog("No need to enable " + provider + ": already enabled"); + break; + } + case PROVIDER_STATE_PERM_FAILED: { + debugLog("Unable to enable " + provider + ": it is perm failed"); + break; + } + default: { + throw new IllegalStateException("Unknown provider state:" + + " provider=" + provider + + ", state=" + providerState.stateEnum); } } } @@ -217,7 +276,9 @@ class ControllerImpl extends LocationTimeZoneProviderController { + " providerState=" + providerState); break; } - case PROVIDER_STATE_ENABLED: { + case PROVIDER_STATE_ENABLED_INITIALIZING: + case PROVIDER_STATE_ENABLED_CERTAIN: + case PROVIDER_STATE_ENABLED_UNCERTAIN: { // Entering enabled does not trigger an event, so this only happens if an event // is received while the provider is enabled. debugLog("onProviderStateChange: Received notification of an event while" @@ -228,10 +289,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { case PROVIDER_STATE_PERM_FAILED: { debugLog("Received notification of permanent failure for" + " provider=" + providerState); - GeolocationTimeZoneSuggestion suggestion = createEmptySuggestion( - "provider=" + providerState.provider - + " permanently failed: " + providerState); - suggestImmediate(suggestion); + providerFailedProcessEvent(); break; } default: { @@ -242,24 +300,46 @@ class ControllerImpl extends LocationTimeZoneProviderController { } private void assertProviderKnown(LocationTimeZoneProvider provider) { - if (provider != mProvider) { + if (provider != mPrimaryProvider) { throw new IllegalArgumentException("Unknown provider: " + provider); } } /** - * Called when a provider has changed state but just moved from a PROVIDER_STATE_ENABLED state - * to another PROVIDER_STATE_ENABLED state, usually as a result of a new {@link - * LocationTimeZoneEvent} being received. There are some cases where event can be null. + * Called when the provider has reported that it has failed permanently. */ + @GuardedBy("mSharedLock") + private void providerFailedProcessEvent() { + // If the provider is newly perm failed then the controller is uncertain by + // definition. + cancelUncertaintyTimeout(); + + // If the provider is now failed, then we must send a suggestion informing the time + // zone detector that there are no further updates coming in future. + + GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( + "The provider is permanently failed:" + + " primary=" + mPrimaryProvider.getCurrentState()); + makeSuggestion(suggestion); + } + + /** + * Called when a provider has changed state but just moved from one enabled state to another + * enabled state, usually as a result of a new {@link LocationTimeZoneEvent} being received. + * However, there are rare cases where the event can be null. + */ + @GuardedBy("mSharedLock") private void providerEnabledProcessEvent(@NonNull ProviderState providerState) { + LocationTimeZoneProvider provider = providerState.provider; LocationTimeZoneEvent event = providerState.event; if (event == null) { // Implicit uncertainty, i.e. where the provider is enabled, but a problem has been // detected without having received an event. For example, if the process has detected - // the loss of a binder-based provider. This is treated like explicit uncertainty, i.e. - // where the provider has explicitly told this process it is uncertain. - scheduleUncertainSuggestionIfNeeded(null); + // the loss of a binder-based provider, or initialization took too long. This is treated + // the same as explicit uncertainty, i.e. where the provider has explicitly told this + // process it is uncertain. + handleProviderUncertainty(provider, "provider=" + provider + + ", implicit uncertainty, event=null"); return; } @@ -279,22 +359,19 @@ class ControllerImpl extends LocationTimeZoneProviderController { switch (event.getEventType()) { case EVENT_TYPE_PERMANENT_FAILURE: { - // This shouldn't happen. Providers cannot be enabled and have this event. + // This shouldn't happen. A provider cannot be enabled and have this event. warnLog("Provider=" + providerState + " is enabled, but event suggests it shouldn't be"); break; } case EVENT_TYPE_UNCERTAIN: { - scheduleUncertainSuggestionIfNeeded(event); + handleProviderUncertainty(provider, "provider=" + provider + + ", explicit uncertainty. event=" + event); break; } case EVENT_TYPE_SUCCESS: { - GeolocationTimeZoneSuggestion suggestion = - new GeolocationTimeZoneSuggestion(event.getTimeZoneIds()); - suggestion.addDebugInfo("Event received provider=" + mProvider.getName() - + ", event=" + event); - // Rely on the receiver to dedupe events. It is better to over-communicate. - suggestImmediate(suggestion); + handleProviderCertainty(provider, event.getTimeZoneIds(), + "Event received provider=" + provider.getName() + ", event=" + event); break; } default: { @@ -304,30 +381,19 @@ class ControllerImpl extends LocationTimeZoneProviderController { } } - /** - * Indicates a provider has become uncertain with the event (if any) received that indicates - * that. - * - * <p>Providers are expected to report their uncertainty as soon as they become uncertain, as - * this enables the most flexibility for the controller to enable other providers when there are - * multiple ones available. The controller is therefore responsible for deciding when to make a - * "uncertain" suggestion. - * - * <p>This method schedules an "uncertain" suggestion (if one isn't already scheduled) to be - * made later if nothing else preempts it. It can be preempted if the provider becomes certain - * (or does anything else that calls {@link #suggestImmediate(GeolocationTimeZoneSuggestion)}) - * within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled - * "uncertain" event to be cancelled. If the provider repeatedly sends uncertainty events within - * the uncertainty delay period, those events are effectively ignored (i.e. the timer is not - * reset each time). - */ - private void scheduleUncertainSuggestionIfNeeded(@Nullable LocationTimeZoneEvent event) { - if (mPendingSuggestion == null || mPendingSuggestion.getZoneIds() != null) { - GeolocationTimeZoneSuggestion suggestion = createEmptySuggestion( - "provider=" + mProvider + " became uncertain, event=" + event); - // Only send the empty suggestion after the uncertainty delay. - suggestDelayed(suggestion, mEnvironment.getUncertaintyDelay()); - } + @GuardedBy("mSharedLock") + private void handleProviderCertainty( + @NonNull LocationTimeZoneProvider provider, + @Nullable List<String> timeZoneIds, + @NonNull String reason) { + // By definition, the controller is now certain. + cancelUncertaintyTimeout(); + + GeolocationTimeZoneSuggestion suggestion = + new GeolocationTimeZoneSuggestion(timeZoneIds); + suggestion.addDebugInfo(reason); + // Rely on the receiver to dedupe events. It is better to over-communicate. + makeSuggestion(suggestion); } @Override @@ -342,66 +408,74 @@ class ControllerImpl extends LocationTimeZoneProviderController { ipw.println("providerInitializationTimeoutFuzz=" + mEnvironment.getProviderInitializationTimeoutFuzz()); ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay()); - ipw.println("mPendingSuggestion=" + mPendingSuggestion); ipw.println("mLastSuggestion=" + mLastSuggestion); - ipw.println("Provider:"); + ipw.println("Primary Provider:"); ipw.increaseIndent(); // level 2 - mProvider.dump(ipw, args); + mPrimaryProvider.dump(ipw, args); ipw.decreaseIndent(); // level 2 ipw.decreaseIndent(); // level 1 } } - /** Sends an immediate suggestion, cancelling any pending suggestion. */ + /** Sends an immediate suggestion, updating mLastSuggestion. */ @GuardedBy("mSharedLock") - private void suggestImmediate(@NonNull GeolocationTimeZoneSuggestion suggestion) { - debugLog("suggestImmediate: Executing suggestion=" + suggestion); - mDelayedSuggestionQueue.runSynchronously(() -> mCallback.suggest(suggestion)); - mPendingSuggestion = null; + private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion) { + debugLog("makeSuggestion: suggestion=" + suggestion); + mCallback.suggest(suggestion); mLastSuggestion = suggestion; } - /** Clears any pending suggestion. */ + /** Clears the uncertainty timeout. */ @GuardedBy("mSharedLock") - private void clearDelayedSuggestion() { - mDelayedSuggestionQueue.cancel(); - mPendingSuggestion = null; + private void cancelUncertaintyTimeout() { + mUncertaintyTimeoutQueue.cancel(); } - /** - * Schedules a delayed suggestion. There can only be one delayed suggestion at a time. - * If there is a pending scheduled suggestion equal to the one passed, it will not be replaced. - * Replacing a previous delayed suggestion has the effect of cancelling the timeout associated - * with that previous suggestion. + * Indicates a provider has become uncertain with the event (if any) received that indicates + * that. + * + * <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as + * this enables the most flexibility for the controller to enable other providers when there are + * multiple ones available. The controller is therefore responsible for deciding when to make a + * "uncertain" suggestion. + * + * <p>This method schedules an "uncertain" suggestion (if one isn't already scheduled) to be + * made later if nothing else preempts it. It can be preempted if the provider becomes certain + * (or does anything else that calls {@link #makeSuggestion(GeolocationTimeZoneSuggestion)}) + * within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled + * "uncertain" event to be cancelled. If the provider repeatedly sends uncertainty events within + * the uncertainty delay period, those events are effectively ignored (i.e. the timer is not + * reset each time). */ @GuardedBy("mSharedLock") - private void suggestDelayed(@NonNull GeolocationTimeZoneSuggestion suggestion, - @NonNull Duration delay) { - Objects.requireNonNull(suggestion); - Objects.requireNonNull(delay); - - if (Objects.equals(mPendingSuggestion, suggestion)) { - // Do not reset the timer. - debugLog("suggestDelayed: Suggestion=" + suggestion + " is equal to existing." - + " Not scheduled."); - return; + void handleProviderUncertainty(@NonNull LocationTimeZoneProvider provider, String reason) { + Objects.requireNonNull(provider); + + // Start the uncertainty timeout if needed. + if (!mUncertaintyTimeoutQueue.hasQueued()) { + debugLog("Starting uncertainty timeout: reason=" + reason); + + Duration delay = mEnvironment.getUncertaintyDelay(); + mUncertaintyTimeoutQueue.runDelayed( + this::onProviderUncertaintyTimeout, delay.toMillis()); } + } - debugLog("suggestDelayed: Scheduling suggestion=" + suggestion); - mPendingSuggestion = suggestion; + private void onProviderUncertaintyTimeout() { + mThreadingDomain.assertCurrentThread(); - mDelayedSuggestionQueue.runDelayed(() -> { - debugLog("suggestDelayed: Executing suggestion=" + suggestion); - mCallback.suggest(suggestion); - mPendingSuggestion = null; - mLastSuggestion = suggestion; - }, delay.toMillis()); + synchronized (mSharedLock) { + GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( + "Uncertainty timeout triggered:" + + " primary=" + mPrimaryProvider.getCurrentState()); + makeSuggestion(suggestion); + } } - private static GeolocationTimeZoneSuggestion createEmptySuggestion(String reason) { + private static GeolocationTimeZoneSuggestion createUncertainSuggestion(String reason) { GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(null); suggestion.addDebugInfo(reason); return suggestion; @@ -412,17 +486,22 @@ class ControllerImpl extends LocationTimeZoneProviderController { * If the provider name does not match a known provider, then the event is logged and discarded. */ void simulateBinderProviderEvent(SimulatedBinderProviderEvent event) { - if (!Objects.equals(mProvider.getName(), event.getProviderName())) { + String targetProviderName = event.getProviderName(); + LocationTimeZoneProvider targetProvider; + if (Objects.equals(mPrimaryProvider.getName(), targetProviderName)) { + targetProvider = mPrimaryProvider; + } else { warnLog("Unable to process simulated binder provider event," + " unknown providerName in event=" + event); return; } - if (!(mProvider instanceof BinderLocationTimeZoneProvider)) { + if (!(targetProvider instanceof BinderLocationTimeZoneProvider)) { warnLog("Unable to process simulated binder provider event," - + " provider is not a " + BinderLocationTimeZoneProvider.class + + " provider=" + targetProvider + + " is not a " + BinderLocationTimeZoneProvider.class + ", event=" + event); return; } - ((BinderLocationTimeZoneProvider) mProvider).simulateBinderProviderEvent(event); + ((BinderLocationTimeZoneProvider) targetProvider).simulateBinderProviderEvent(event); } } 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 238f999ff8a6..c9a211d96fc0 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java @@ -160,7 +160,8 @@ public class LocationTimeZoneManagerService extends Binder { // Called on an arbitrary thread during initialization. synchronized (mSharedLock) { LocationTimeZoneProvider primary = createPrimaryProvider(); - mLocationTimeZoneDetectorController = new ControllerImpl(mThreadingDomain, primary); + mLocationTimeZoneDetectorController = + new ControllerImpl(mThreadingDomain, primary); ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain); ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl( mThreadingDomain, mLocationTimeZoneDetectorController); 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 abfa580b5c98..4b0b5a2cbbe4 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java @@ -22,7 +22,9 @@ import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTA import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; import android.annotation.IntDef; @@ -33,6 +35,9 @@ import android.os.Handler; import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; +import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.Dumpable; import com.android.server.timezonedetector.ReferenceWithHistory; @@ -73,8 +78,9 @@ abstract class LocationTimeZoneProvider implements Dumpable { */ static class ProviderState { - @IntDef({ PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_ENABLED, PROVIDER_STATE_DISABLED, - PROVIDER_STATE_PERM_FAILED }) + @IntDef({ PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_ENABLED_INITIALIZING, + PROVIDER_STATE_ENABLED_CERTAIN, PROVIDER_STATE_ENABLED_UNCERTAIN, + PROVIDER_STATE_DISABLED, PROVIDER_STATE_PERM_FAILED }) @interface ProviderStateEnum {} /** @@ -83,22 +89,33 @@ abstract class LocationTimeZoneProvider implements Dumpable { static final int PROVIDER_STATE_UNKNOWN = 0; /** - * The provider is currently enabled. + * The provider is enabled and has not reported its first event. */ - static final int PROVIDER_STATE_ENABLED = 1; + static final int PROVIDER_STATE_ENABLED_INITIALIZING = 1; /** - * The provider is currently disabled. + * The provider is enabled and most recently reported a "success" event. + */ + static final int PROVIDER_STATE_ENABLED_CERTAIN = 2; + + /** + * The provider is enabled and most recently reported an "uncertain" event. + */ + static final int PROVIDER_STATE_ENABLED_UNCERTAIN = 3; + + /** + * The provider is disabled. + * * This is the state after {@link #initialize} is called. */ - static final int PROVIDER_STATE_DISABLED = 2; + static final int PROVIDER_STATE_DISABLED = 4; /** * The provider has failed and cannot be re-enabled. * * Providers may enter this state after a provider is enabled. */ - static final int PROVIDER_STATE_PERM_FAILED = 3; + static final int PROVIDER_STATE_PERM_FAILED = 5; /** The {@link LocationTimeZoneProvider} the state is for. */ public final @NonNull LocationTimeZoneProvider provider; @@ -108,14 +125,15 @@ abstract class LocationTimeZoneProvider implements Dumpable { /** * The last {@link LocationTimeZoneEvent} received. Only populated when {@link #stateEnum} - * is {@link #PROVIDER_STATE_ENABLED}, but it can be {@code null} then too if no event has + * is either {@link #PROVIDER_STATE_ENABLED_CERTAIN} or {@link + * #PROVIDER_STATE_ENABLED_UNCERTAIN}, but it can be {@code null} then too if no event has * yet been received. */ @Nullable public final LocationTimeZoneEvent event; /** * The user configuration associated with the current state. Only and always present when - * {@link #stateEnum} is {@link #PROVIDER_STATE_ENABLED}. + * {@link #stateEnum} is one of the enabled states. */ @Nullable public final ConfigurationInternal currentUserConfiguration; @@ -133,7 +151,8 @@ abstract class LocationTimeZoneProvider implements Dumpable { private ProviderState(@NonNull LocationTimeZoneProvider provider, - @ProviderStateEnum int stateEnum, @Nullable LocationTimeZoneEvent event, + @ProviderStateEnum int stateEnum, + @Nullable LocationTimeZoneEvent event, @Nullable ConfigurationInternal currentUserConfiguration, @Nullable String debugInfo) { this.provider = Objects.requireNonNull(provider); @@ -172,7 +191,9 @@ abstract class LocationTimeZoneProvider implements Dumpable { break; } case PROVIDER_STATE_DISABLED: - case PROVIDER_STATE_ENABLED: { + case PROVIDER_STATE_ENABLED_INITIALIZING: + case PROVIDER_STATE_ENABLED_CERTAIN: + case PROVIDER_STATE_ENABLED_UNCERTAIN: { // These can go to each other or PROVIDER_STATE_PERM_FAILED. break; } @@ -200,7 +221,9 @@ abstract class LocationTimeZoneProvider implements Dumpable { } break; } - case PROVIDER_STATE_ENABLED: { + case PROVIDER_STATE_ENABLED_INITIALIZING: + case PROVIDER_STATE_ENABLED_CERTAIN: + case PROVIDER_STATE_ENABLED_UNCERTAIN: { if (currentUserConfig == null) { throw new IllegalArgumentException( "Enabled state: currentUserConfig must not be null"); @@ -223,6 +246,13 @@ abstract class LocationTimeZoneProvider implements Dumpable { return new ProviderState(provider, newStateEnum, event, currentUserConfig, debugInfo); } + /** Returns {@code true} if {@link #stateEnum} is one of the enabled states. */ + boolean isEnabled() { + return stateEnum == PROVIDER_STATE_ENABLED_INITIALIZING + || stateEnum == PROVIDER_STATE_ENABLED_CERTAIN + || stateEnum == PROVIDER_STATE_ENABLED_UNCERTAIN; + } + @Override public String toString() { return "State{" @@ -257,8 +287,12 @@ abstract class LocationTimeZoneProvider implements Dumpable { switch (state) { case PROVIDER_STATE_DISABLED: return "Disabled (" + PROVIDER_STATE_DISABLED + ")"; - case PROVIDER_STATE_ENABLED: - return "Enabled (" + PROVIDER_STATE_ENABLED + ")"; + case PROVIDER_STATE_ENABLED_INITIALIZING: + return "Enabled initializing (" + PROVIDER_STATE_ENABLED_INITIALIZING + ")"; + case PROVIDER_STATE_ENABLED_CERTAIN: + return "Enabled certain (" + PROVIDER_STATE_ENABLED_CERTAIN + ")"; + case PROVIDER_STATE_ENABLED_UNCERTAIN: + return "Enabled uncertain (" + PROVIDER_STATE_ENABLED_UNCERTAIN + ")"; case PROVIDER_STATE_PERM_FAILED: return "Perm failure (" + PROVIDER_STATE_PERM_FAILED + ")"; case PROVIDER_STATE_UNKNOWN: @@ -279,6 +313,11 @@ abstract class LocationTimeZoneProvider implements Dumpable { final ReferenceWithHistory<ProviderState> mCurrentState = new ReferenceWithHistory<>(10); + /** + * Used for scheduling initialization timeouts, i.e. for providers that have just been enabled. + */ + @NonNull private final SingleRunnableQueue mInitializationTimeoutQueue; + // Non-null and effectively final after initialize() is called. ProviderListener mProviderListener; @@ -286,6 +325,7 @@ abstract class LocationTimeZoneProvider implements Dumpable { LocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain, @NonNull String providerName) { mThreadingDomain = Objects.requireNonNull(threadingDomain); + mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue(); mSharedLock = threadingDomain.getLockObject(); mProviderName = Objects.requireNonNull(providerName); } @@ -303,7 +343,8 @@ abstract class LocationTimeZoneProvider implements Dumpable { mProviderListener = Objects.requireNonNull(providerListener); ProviderState currentState = ProviderState.createStartingState(this); ProviderState newState = currentState.newState( - PROVIDER_STATE_DISABLED, null, null, "initialize() called"); + PROVIDER_STATE_DISABLED, null, null, + "initialize() called"); setCurrentState(newState, false); onInitialize(); @@ -370,20 +411,41 @@ abstract class LocationTimeZoneProvider implements Dumpable { * called using the handler thread from the {@link ThreadingDomain}. */ final void enable(@NonNull ConfigurationInternal currentUserConfiguration, - @NonNull Duration initializationTimeout) { + @NonNull Duration initializationTimeout, @NonNull Duration initializationTimeoutFuzz) { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { assertCurrentState(PROVIDER_STATE_DISABLED); - ProviderState currentState = getCurrentState(); + ProviderState currentState = mCurrentState.get(); ProviderState newState = currentState.newState( - PROVIDER_STATE_ENABLED, null, currentUserConfiguration, "enable() called"); + PROVIDER_STATE_ENABLED_INITIALIZING, null /* event */, + currentUserConfiguration, "enable() called"); setCurrentState(newState, false); + + Duration delay = initializationTimeout.plus(initializationTimeoutFuzz); + mInitializationTimeoutQueue.runDelayed( + this::handleInitializationTimeout, delay.toMillis()); + onEnable(initializationTimeout); } } + private void handleInitializationTimeout() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + ProviderState currentState = mCurrentState.get(); + if (currentState.stateEnum == PROVIDER_STATE_ENABLED_INITIALIZING) { + // On initialization timeout the provider becomes uncertain. + ProviderState newState = currentState.newState( + PROVIDER_STATE_ENABLED_UNCERTAIN, null /* event */, + currentState.currentUserConfiguration, "initialization timeout"); + setCurrentState(newState, true); + } + } + } + /** * Implemented by subclasses to do work during {@link #enable}. */ @@ -391,20 +453,24 @@ abstract class LocationTimeZoneProvider implements Dumpable { /** * Disables the provider. It is an error* to call this method except when the {@link - * #getCurrentState()} is at {@link ProviderState#PROVIDER_STATE_ENABLED}. This method must be + * #getCurrentState()} is one of the enabled states. This method must be * called using the handler thread from the {@link ThreadingDomain}. */ final void disable() { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { - assertCurrentState(PROVIDER_STATE_ENABLED); + assertIsEnabled(); - ProviderState currentState = getCurrentState(); - ProviderState newState = - currentState.newState(PROVIDER_STATE_DISABLED, null, null, "disable() called"); + ProviderState currentState = mCurrentState.get(); + ProviderState newState = currentState.newState( + PROVIDER_STATE_DISABLED, null, null, "disable() called"); setCurrentState(newState, false); + if (mInitializationTimeoutQueue.hasQueued()) { + mInitializationTimeoutQueue.cancel(); + } + onDisable(); } } @@ -424,7 +490,7 @@ abstract class LocationTimeZoneProvider implements Dumpable { debugLog("handleLocationTimeZoneEvent: mProviderName=" + mProviderName + ", locationTimeZoneEvent=" + locationTimeZoneEvent); - ProviderState currentState = getCurrentState(); + ProviderState currentState = mCurrentState.get(); int eventType = locationTimeZoneEvent.getEventType(); switch (currentState.stateEnum) { case PROVIDER_STATE_PERM_FAILED: { @@ -445,6 +511,9 @@ abstract class LocationTimeZoneProvider implements Dumpable { ProviderState newState = currentState.newState( PROVIDER_STATE_PERM_FAILED, null, null, msg); setCurrentState(newState, true); + if (mInitializationTimeoutQueue.hasQueued()) { + mInitializationTimeoutQueue.cancel(); + } return; } case EVENT_TYPE_SUCCESS: @@ -464,7 +533,9 @@ abstract class LocationTimeZoneProvider implements Dumpable { } } } - case PROVIDER_STATE_ENABLED: { + case PROVIDER_STATE_ENABLED_INITIALIZING: + case PROVIDER_STATE_ENABLED_CERTAIN: + case PROVIDER_STATE_ENABLED_UNCERTAIN: { switch (eventType) { case EVENT_TYPE_PERMANENT_FAILURE: { String msg = "handleLocationTimeZoneEvent:" @@ -475,14 +546,27 @@ abstract class LocationTimeZoneProvider implements Dumpable { ProviderState newState = currentState.newState( PROVIDER_STATE_PERM_FAILED, null, null, msg); setCurrentState(newState, true); + if (mInitializationTimeoutQueue.hasQueued()) { + mInitializationTimeoutQueue.cancel(); + } + return; } case EVENT_TYPE_UNCERTAIN: case EVENT_TYPE_SUCCESS: { - ProviderState newState = currentState.newState(PROVIDER_STATE_ENABLED, + @ProviderStateEnum int providerStateEnum; + if (eventType == EVENT_TYPE_UNCERTAIN) { + providerStateEnum = PROVIDER_STATE_ENABLED_UNCERTAIN; + } else { + providerStateEnum = PROVIDER_STATE_ENABLED_CERTAIN; + } + ProviderState newState = currentState.newState(providerStateEnum, locationTimeZoneEvent, currentState.currentUserConfiguration, "handleLocationTimeZoneEvent() when enabled"); setCurrentState(newState, true); + if (mInitializationTimeoutQueue.hasQueued()) { + mInitializationTimeoutQueue.cancel(); + } return; } default: { @@ -503,11 +587,34 @@ abstract class LocationTimeZoneProvider implements Dumpable { */ abstract void logWarn(String msg); - private void assertCurrentState(@ProviderState.ProviderStateEnum int requiredState) { - ProviderState currentState = getCurrentState(); + @GuardedBy("mSharedLock") + private void assertIsEnabled() { + ProviderState currentState = mCurrentState.get(); + if (!currentState.isEnabled()) { + throw new IllegalStateException("Required an enabled state, but was " + currentState); + } + } + + @GuardedBy("mSharedLock") + private void assertCurrentState(@ProviderStateEnum int requiredState) { + ProviderState currentState = mCurrentState.get(); if (currentState.stateEnum != requiredState) { throw new IllegalStateException( "Required stateEnum=" + requiredState + ", but was " + currentState); } } + + @VisibleForTesting + boolean isInitializationTimeoutSet() { + synchronized (mSharedLock) { + return mInitializationTimeoutQueue.hasQueued(); + } + } + + @VisibleForTesting + Duration getInitializationTimeoutDelay() { + synchronized (mSharedLock) { + return Duration.ofMillis(mInitializationTimeoutQueue.getQueuedDelayMillis()); + } + } } diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java index 88f0f00015c6..ace066e12d3f 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java @@ -19,6 +19,7 @@ package com.android.server.location.timezone; import android.annotation.NonNull; import android.os.Handler; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.Dumpable; @@ -85,6 +86,12 @@ abstract class LocationTimeZoneProviderController implements Dumpable { */ abstract void onConfigChanged(); + @VisibleForTesting + abstract boolean isUncertaintyTimeoutSet(); + + @VisibleForTesting + abstract long getUncertaintyTimeoutDelayMillis(); + /** * Used by {@link LocationTimeZoneProviderController} to obtain information from the surrounding * service. It can easily be faked for tests. diff --git a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java new file mode 100644 index 000000000000..38018779aed1 --- /dev/null +++ b/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java @@ -0,0 +1,174 @@ +/* + * 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 android.content.ComponentName; +import android.content.Context; +import android.location.timezone.LocationTimeZoneEvent; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.IndentingPrintWriter; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.location.timezone.ILocationTimeZoneProvider; +import com.android.internal.location.timezone.ILocationTimeZoneProviderManager; +import com.android.internal.location.timezone.LocationTimeZoneProviderRequest; +import com.android.server.ServiceWatcher; + +import java.util.Objects; + +/** + * System server-side proxy for ILocationTimeZoneProvider implementations, i.e. this provides the + * system server object used to communicate with a remote LocationTimeZoneProvider over Binder, + * which could be running in a different process. As "remote" LocationTimeZoneProviders are bound / + * unbound this proxy will rebind to the "best" available remote process. + */ +class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { + + /** + * Creates and registers this proxy. If no suitable service is available for the proxy, returns + * null. + */ + @Nullable + static LocationTimeZoneProviderProxy createAndRegister( + @NonNull Context context, @NonNull ThreadingDomain threadingDomain, + @NonNull String action, int enableOverlayResId, int nonOverlayPackageResId) { + RealLocationTimeZoneProviderProxy proxy = new RealLocationTimeZoneProviderProxy( + context, threadingDomain, action, enableOverlayResId, nonOverlayPackageResId); + if (proxy.register()) { + return proxy; + } else { + return null; + } + } + + @NonNull private final ServiceWatcher mServiceWatcher; + + @GuardedBy("mProxyLock") + @Nullable private ManagerProxy mManagerProxy; + + @GuardedBy("mProxyLock") + @NonNull private LocationTimeZoneProviderRequest mRequest; + + private RealLocationTimeZoneProviderProxy( + @NonNull Context context, @NonNull ThreadingDomain threadingDomain, + @NonNull String action, int enableOverlayResId, + int nonOverlayPackageResId) { + super(context, threadingDomain); + mManagerProxy = null; + mRequest = LocationTimeZoneProviderRequest.EMPTY_REQUEST; + mServiceWatcher = new ServiceWatcher(context, action, this::onBind, this::onUnbind, + enableOverlayResId, nonOverlayPackageResId); + } + + private boolean register() { + return mServiceWatcher.register(); + } + + private void onBind(IBinder binder, ComponentName componentName) throws RemoteException { + processServiceWatcherCallbackOnThreadingDomainThread(() -> onBindOnHandlerThread(binder)); + } + + private void onUnbind() { + processServiceWatcherCallbackOnThreadingDomainThread(this::onUnbindOnHandlerThread); + } + + private void processServiceWatcherCallbackOnThreadingDomainThread(@NonNull Runnable runnable) { + // For simplicity, this code just post()s the runnable to the mThreadingDomain Thread in all + // cases. This adds a delay if ServiceWatcher and ThreadingDomain happen to be using the + // same thread, but nothing here should be performance critical. + mThreadingDomain.post(runnable); + } + + private void onBindOnHandlerThread(@NonNull IBinder binder) { + mThreadingDomain.assertCurrentThread(); + + ILocationTimeZoneProvider provider = ILocationTimeZoneProvider.Stub.asInterface(binder); + + synchronized (mSharedLock) { + try { + mManagerProxy = new ManagerProxy(); + provider.setLocationTimeZoneProviderManager(mManagerProxy); + trySendCurrentRequest(); + mListener.onProviderBound(); + } catch (RemoteException e) { + // This is not expected to happen. + throw new RuntimeException(e); + } + } + } + + private void onUnbindOnHandlerThread() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + mManagerProxy = null; + mListener.onProviderUnbound(); + } + } + + @Override + final void setRequest(@NonNull LocationTimeZoneProviderRequest request) { + mThreadingDomain.assertCurrentThread(); + + Objects.requireNonNull(request); + synchronized (mSharedLock) { + mRequest = request; + + trySendCurrentRequest(); + } + } + + @GuardedBy("mProxyLock") + private void trySendCurrentRequest() { + LocationTimeZoneProviderRequest request = mRequest; + mServiceWatcher.runOnBinder(binder -> { + ILocationTimeZoneProvider service = + ILocationTimeZoneProvider.Stub.asInterface(binder); + service.setRequest(request); + }); + } + + @Override + public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) { + synchronized (mSharedLock) { + ipw.println("mRequest=" + mRequest); + mServiceWatcher.dump(null, ipw, args); + } + } + + /** + * A system Server-side proxy for the ILocationTimeZoneProviderManager, i.e. this is a local + * binder stub. Each "remote" LocationTimeZoneProvider is passed a binder instance that it + * then uses to communicate back with the system server, invoking the logic here. + */ + private class ManagerProxy extends ILocationTimeZoneProviderManager.Stub { + + // executed on binder thread + @Override + public void onLocationTimeZoneEvent(LocationTimeZoneEvent locationTimeZoneEvent) { + synchronized (mSharedLock) { + if (mManagerProxy != this) { + return; + } + } + handleLocationTimeZoneEvent(locationTimeZoneEvent); + } + } +} diff --git a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java b/services/core/java/com/android/server/location/timezone/ThreadingDomain.java index 9b9c82358974..d55d3edf5e03 100644 --- a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java +++ b/services/core/java/com/android/server/location/timezone/ThreadingDomain.java @@ -83,21 +83,14 @@ abstract class ThreadingDomain { } /** - * A class that allows up to one {@link Runnable} to be queued on the handler, i.e. calling any - * of the methods will cancel the execution of any previously queued / delayed runnable. All + * A class that allows up to one {@link Runnable} to be queued, i.e. calling {@link + * #runDelayed(Runnable, long)} will cancel the execution of any previously queued runnable. All * methods must be called from the {@link ThreadingDomain}'s thread. */ final class SingleRunnableQueue { - /** - * Runs the supplied {@link Runnable} synchronously on the threading domain's thread, - * cancelling any queued but not-yet-executed {@link Runnable} previously added by this. - * This method must be called from the threading domain's thread. - */ - void runSynchronously(Runnable r) { - cancel(); - r.run(); - } + private boolean mIsQueued; + private long mDelayMillis; /** * Posts the supplied {@link Runnable} asynchronously and delayed on the threading domain @@ -106,15 +99,48 @@ abstract class ThreadingDomain { */ void runDelayed(Runnable r, long delayMillis) { cancel(); - ThreadingDomain.this.postDelayed(r, this, delayMillis); + mIsQueued = true; + mDelayMillis = delayMillis; + ThreadingDomain.this.postDelayed(() -> { + mIsQueued = false; + mDelayMillis = -2; + r.run(); + }, this, delayMillis); + } + + /** + * Returns {@code true} if there is an item current queued. This method must be called from + * the threading domain's thread. + */ + boolean hasQueued() { + assertCurrentThread(); + return mIsQueued; + } + + /** + * Returns the delay in milliseconds for the currently queued item. Throws {@link + * IllegalStateException} if nothing is currently queued, see {@link #hasQueued()}. + * This method must be called from the threading domain's thread. + */ + long getQueuedDelayMillis() { + assertCurrentThread(); + if (!mIsQueued) { + throw new IllegalStateException("No item queued"); + } + return mDelayMillis; } /** * Cancels any queued but not-yet-executed {@link Runnable} previously added by this. + * This method must be called from the threading domain's thread. */ public void cancel() { assertCurrentThread(); - removeQueuedRunnables(this); + if (mIsQueued) { + removeQueuedRunnables(this); + } + mIsQueued = false; + mDelayMillis = -1; } } } diff --git a/services/core/java/com/android/server/location/util/AlarmHelper.java b/services/core/java/com/android/server/location/util/AlarmHelper.java new file mode 100644 index 000000000000..46dbdbffd87e --- /dev/null +++ b/services/core/java/com/android/server/location/util/AlarmHelper.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.util; + +import android.app.AlarmManager.OnAlarmListener; +import android.os.WorkSource; + +import com.android.internal.util.Preconditions; + +/** + * Helps manage alarms. + */ +public abstract class AlarmHelper { + + /** + * Sets a wakeup alarm that will fire after the given delay. + */ + public final void setDelayedAlarm(long delayMs, OnAlarmListener listener, + WorkSource workSource) { + // helps ensure that we're not wasting system resources by setting alarms in the past/now + Preconditions.checkArgument(delayMs > 0); + Preconditions.checkArgument(workSource != null); + setDelayedAlarmInternal(delayMs, listener, workSource); + } + + protected abstract void setDelayedAlarmInternal(long delayMs, OnAlarmListener listener, + WorkSource workSource); + + /** + * Cancels an alarm. + */ + public abstract void cancel(OnAlarmListener listener); +} diff --git a/services/core/java/com/android/server/location/util/Injector.java b/services/core/java/com/android/server/location/util/Injector.java index 379b303bbfc3..d9c73ba46444 100644 --- a/services/core/java/com/android/server/location/util/Injector.java +++ b/services/core/java/com/android/server/location/util/Injector.java @@ -28,6 +28,9 @@ public interface Injector { /** Returns a UserInfoHelper. */ UserInfoHelper getUserInfoHelper(); + /** Returns an AlarmHelper. */ + AlarmHelper getAlarmHelper(); + /** Returns an AppOpsHelper. */ AppOpsHelper getAppOpsHelper(); diff --git a/services/core/java/com/android/server/location/util/SystemAlarmHelper.java b/services/core/java/com/android/server/location/util/SystemAlarmHelper.java new file mode 100644 index 000000000000..81849794c472 --- /dev/null +++ b/services/core/java/com/android/server/location/util/SystemAlarmHelper.java @@ -0,0 +1,57 @@ +/* + * 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.util; + +import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; +import static android.app.AlarmManager.WINDOW_EXACT; + +import android.app.AlarmManager; +import android.content.Context; +import android.os.SystemClock; +import android.os.WorkSource; + +import com.android.server.FgThread; + +import java.util.Objects; + +/** + * Provides helpers for alarms. + */ +public class SystemAlarmHelper extends AlarmHelper { + + private final Context mContext; + + public SystemAlarmHelper(Context context) { + mContext = context; + } + + @Override + public void setDelayedAlarmInternal(long delayMs, AlarmManager.OnAlarmListener listener, + WorkSource workSource) { + AlarmManager alarmManager = Objects.requireNonNull( + mContext.getSystemService(AlarmManager.class)); + alarmManager.set(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayMs, + WINDOW_EXACT, 0, listener, FgThread.getHandler(), workSource); + } + + @Override + public void cancel(AlarmManager.OnAlarmListener listener) { + AlarmManager alarmManager = Objects.requireNonNull( + mContext.getSystemService(AlarmManager.class)); + alarmManager.cancel(listener); + } +} diff --git a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java index ddd56c890c2f..82b0f9c05b6b 100644 --- a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java +++ b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java @@ -20,9 +20,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.face.FaceManager; -import android.hardware.face.FaceSensorProperties; +import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; -import android.hardware.fingerprint.FingerprintSensorProperties; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Handler; import android.os.IBinder; import android.os.ServiceManager; @@ -34,7 +34,6 @@ import com.android.internal.widget.VerifyCredentialResponse; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Set; /** @@ -203,9 +202,9 @@ public class BiometricDeferredQueue { private void processPendingLockoutsForFingerprint(List<UserAuthInfo> pendingResetLockouts) { if (mFingerprintManager != null) { - final List<FingerprintSensorProperties> fingerprintSensorProperties = - mFingerprintManager.getSensorProperties(); - for (FingerprintSensorProperties prop : fingerprintSensorProperties) { + final List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties = + mFingerprintManager.getSensorPropertiesInternal(); + for (FingerprintSensorPropertiesInternal prop : fingerprintSensorProperties) { if (!prop.resetLockoutRequiresHardwareAuthToken) { for (UserAuthInfo user : pendingResetLockouts) { mFingerprintManager.resetLockout(prop.sensorId, user.userId, @@ -238,16 +237,16 @@ public class BiometricDeferredQueue { Slog.w(TAG, "mFaceGenerateChallengeCallback not null, previous operation may be" + " stuck"); } - final List<FaceSensorProperties> faceSensorProperties = - mFaceManager.getSensorProperties(); + final List<FaceSensorPropertiesInternal> faceSensorProperties = + mFaceManager.getSensorPropertiesInternal(); final Set<Integer> sensorIds = new ArraySet<>(); - for (FaceSensorProperties prop : faceSensorProperties) { + for (FaceSensorPropertiesInternal prop : faceSensorProperties) { sensorIds.add(prop.sensorId); } mFaceResetLockoutTask = new FaceResetLockoutTask(mFaceFinishCallback, mFaceManager, mSpManager, sensorIds, pendingResetLockouts); - for (final FaceSensorProperties prop : faceSensorProperties) { + for (final FaceSensorPropertiesInternal prop : faceSensorProperties) { // Generate a challenge for each sensor. The challenge does not need to be // per-user, since the HAT returned by gatekeeper contains userId. mFaceManager.generateChallenge(prop.sensorId, mFaceResetLockoutTask); diff --git a/services/core/java/com/android/server/media/MediaResourceMonitorService.java b/services/core/java/com/android/server/media/MediaResourceMonitorService.java index e5da9b1c178c..fc7dd30eec49 100644 --- a/services/core/java/com/android/server/media/MediaResourceMonitorService.java +++ b/services/core/java/com/android/server/media/MediaResourceMonitorService.java @@ -28,6 +28,8 @@ import android.util.Log; import com.android.server.SystemService; +import java.util.List; + /** This class provides a system service that monitors media resource usage. */ public class MediaResourceMonitorService extends SystemService { private static final String TAG = "MediaResourceMonitor"; @@ -60,16 +62,18 @@ public class MediaResourceMonitorService extends SystemService { if (pkgNames == null) { return; } - UserManager manager = getContext().getSystemService(UserManager.class); - int[] userIds = manager.getEnabledProfileIds(ActivityManager.getCurrentUser()); - if (userIds == null || userIds.length == 0) { + UserManager manager = getContext().createContextAsUser( + UserHandle.of(ActivityManager.getCurrentUser()), /*flags=*/0) + .getSystemService(UserManager.class); + List<UserHandle> enabledProfiles = manager.getEnabledProfiles(); + if (enabledProfiles.isEmpty()) { return; } Intent intent = new Intent(Intent.ACTION_MEDIA_RESOURCE_GRANTED); intent.putExtra(Intent.EXTRA_PACKAGES, pkgNames); intent.putExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE, type); - for (int userId : userIds) { - getContext().sendBroadcastAsUser(intent, UserHandle.of(userId), + for (UserHandle userHandle : enabledProfiles) { + getContext().sendBroadcastAsUser(intent, userHandle, android.Manifest.permission.RECEIVE_MEDIA_RESOURCE_USAGE); } } finally { diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java index 20df271a1de2..69c57a9a5d74 100644 --- a/services/core/java/com/android/server/media/MediaShellCommand.java +++ b/services/core/java/com/android/server/media/MediaShellCommand.java @@ -38,6 +38,7 @@ import android.view.KeyEvent; import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.List; @@ -53,11 +54,13 @@ public class MediaShellCommand extends ShellCommand { private ISessionManager mSessionService; private PrintWriter mWriter; private PrintWriter mErrorWriter; + private InputStream mInput; @Override public int onCommand(String cmd) { mWriter = getOutPrintWriter(); mErrorWriter = getErrPrintWriter(); + mInput = getRawInputStream(); if (TextUtils.isEmpty(cmd)) { return handleDefaultCommands(cmd); @@ -189,6 +192,10 @@ public class MediaShellCommand extends ShellCommand { KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD)); } + void log(String code, String msg) { + mWriter.println(code + " " + msg); + } + void showError(String errMsg) { onHelp(); mErrorWriter.println(errMsg); @@ -273,11 +280,14 @@ public class MediaShellCommand extends ShellCommand { cbThread.start(); try { - InputStreamReader converter = new InputStreamReader(System.in); + InputStreamReader converter = new InputStreamReader(mInput); BufferedReader in = new BufferedReader(converter); String line; - while ((line = in.readLine()) != null) { + while (true) { + mWriter.flush(); + mErrorWriter.flush(); + if ((line = in.readLine()) == null) break; boolean addNewline = true; if (line.length() <= 0) { addNewline = false; @@ -297,7 +307,7 @@ public class MediaShellCommand extends ShellCommand { synchronized (this) { if (addNewline) { - System.out.println(""); + mWriter.println(""); } printUsageMessage(); } diff --git a/services/core/java/com/android/server/media/VolumeCtrl.java b/services/core/java/com/android/server/media/VolumeCtrl.java index 7a2666566ea2..d516d963e866 100644 --- a/services/core/java/com/android/server/media/VolumeCtrl.java +++ b/services/core/java/com/android/server/media/VolumeCtrl.java @@ -32,6 +32,8 @@ import com.android.internal.os.BaseCommand; public class VolumeCtrl { private static final String TAG = "VolumeCtrl"; + private static final String LOG_V = "[V]"; + private static final String LOG_E = "[E]"; // --stream affects --set, --adj or --get options. // --show affects --set and --adj options. @@ -80,21 +82,22 @@ public class VolumeCtrl { break; case "--get": doGet = true; - log(LOG_V, "will get volume"); + cmd.log(LOG_V, "will get volume"); break; case "--stream": stream = Integer.decode(cmd.getNextArgRequired()).intValue(); - log(LOG_V, "will control stream=" + stream + " (" + streamName(stream) + ")"); + cmd.log(LOG_V, + "will control stream=" + stream + " (" + streamName(stream) + ")"); break; case "--set": volIndex = Integer.decode(cmd.getNextArgRequired()).intValue(); mode = VOLUME_CONTROL_MODE_SET; - log(LOG_V, "will set volume to index=" + volIndex); + cmd.log(LOG_V, "will set volume to index=" + volIndex); break; case "--adj": mode = VOLUME_CONTROL_MODE_ADJUST; adjustment = cmd.getNextArgRequired(); - log(LOG_V, "will adjust volume"); + cmd.log(LOG_V, "will adjust volume"); break; default: throw new IllegalArgumentException("Unknown argument " + option); @@ -122,11 +125,11 @@ public class VolumeCtrl { //---------------------------------------- // Test initialization - log(LOG_V, "Connecting to AudioService"); + cmd.log(LOG_V, "Connecting to AudioService"); IAudioService audioService = IAudioService.Stub.asInterface(ServiceManager.checkService( Context.AUDIO_SERVICE)); if (audioService == null) { - System.err.println(BaseCommand.NO_SYSTEM_ERROR_CODE); + cmd.log(LOG_E, BaseCommand.NO_SYSTEM_ERROR_CODE); throw new AndroidException( "Can't connect to audio service; is the system running?"); } @@ -152,23 +155,12 @@ public class VolumeCtrl { audioService.adjustStreamVolume(stream, adjDir, flag, pack); } if (doGet) { - log(LOG_V, "volume is " + audioService.getStreamVolume(stream) + cmd.log(LOG_V, "volume is " + audioService.getStreamVolume(stream) + " in range [" + audioService.getStreamMinVolume(stream) + ".." + audioService.getStreamMaxVolume(stream) + "]"); } } - //-------------------------------------------- - // Utilities - - static final String LOG_V = "[v]"; - static final String LOG_W = "[w]"; - static final String LOG_OK = "[ok]"; - - static void log(String code, String msg) { - System.out.println(code + " " + msg); - } - static String streamName(int stream) { try { return AudioSystem.STREAM_NAMES[stream]; diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java index 18da33ce19e0..7257f522221f 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java @@ -40,15 +40,12 @@ import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.LinkedList; -import java.util.concurrent.TimeUnit; +import java.util.Set; /** * Provides an interface to write and query for notification history data for a user from a Protocol @@ -173,8 +170,8 @@ public class NotificationHistoryDatabase { mFileWriteHandler.post(rnr); } - public void deleteConversation(String pkg, String conversationId) { - RemoveConversationRunnable rcr = new RemoveConversationRunnable(pkg, conversationId); + public void deleteConversations(String pkg, Set<String> conversationIds) { + RemoveConversationRunnable rcr = new RemoveConversationRunnable(pkg, conversationIds); mFileWriteHandler.post(rcr); } @@ -467,12 +464,12 @@ public class NotificationHistoryDatabase { final class RemoveConversationRunnable implements Runnable { private String mPkg; - private String mConversationId; + private Set<String> mConversationIds; private NotificationHistory mNotificationHistory; - public RemoveConversationRunnable(String pkg, String conversationId) { + public RemoveConversationRunnable(String pkg, Set<String> conversationIds) { mPkg = pkg; - mConversationId = conversationId; + mConversationIds = conversationIds; } @VisibleForTesting @@ -482,10 +479,10 @@ public class NotificationHistoryDatabase { @Override public void run() { - if (DEBUG) Slog.d(TAG, "RemoveConversationRunnable " + mPkg + " " + mConversationId); + if (DEBUG) Slog.d(TAG, "RemoveConversationRunnable " + mPkg + " " + mConversationIds); synchronized (mLock) { // Remove from pending history - mBuffer.removeConversationFromWrite(mPkg, mConversationId); + mBuffer.removeConversationsFromWrite(mPkg, mConversationIds); Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator(); while (historyFileItr.hasNext()) { @@ -496,7 +493,8 @@ public class NotificationHistoryDatabase { : new NotificationHistory(); readLocked(af, notificationHistory, new NotificationHistoryFilter.Builder().build()); - if(notificationHistory.removeConversationFromWrite(mPkg, mConversationId)) { + if (notificationHistory.removeConversationsFromWrite( + mPkg, mConversationIds)) { writeLocked(af, notificationHistory); } } catch (Exception e) { diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java index 69a7ce90f1c6..cf3530bfe7fc 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java @@ -38,12 +38,12 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.FunctionalUtils; import com.android.server.IoThread; import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Set; /** * Keeps track of per-user notification histories. @@ -167,7 +167,7 @@ public class NotificationHistoryManager { } } - public void deleteConversation(String pkg, int uid, String conversationId) { + public void deleteConversations(String pkg, int uid, Set<String> conversationIds) { synchronized (mLock) { int userId = UserHandle.getUserId(uid); final NotificationHistoryDatabase userHistory = @@ -179,7 +179,7 @@ public class NotificationHistoryManager { + userId); return; } - userHistory.deleteConversation(pkg, conversationId); + userHistory.deleteConversations(pkg, conversationIds); } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index c301cd2339ec..affdcea1960b 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -19,6 +19,8 @@ package com.android.server.notification; import android.app.Notification; import android.app.NotificationChannel; +import java.util.Set; + public interface NotificationManagerInternal { NotificationChannel getNotificationChannel(String pkg, int uid, String channelId); void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid, @@ -28,5 +30,5 @@ public interface NotificationManagerInternal { void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId); - void onConversationRemoved(String pkg, int uid, String conversationId); + void onConversationRemoved(String pkg, int uid, Set<String> shortcuts); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 79882dab48a1..b4c98e06a442 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5605,8 +5605,8 @@ public class NotificationManagerService extends SystemService { } @Override - public void onConversationRemoved(String pkg, int uid, String conversationId) { - onConversationRemovedInternal(pkg, uid, conversationId); + public void onConversationRemoved(String pkg, int uid, Set<String> shortcuts) { + onConversationRemovedInternal(pkg, uid, shortcuts); } @GuardedBy("mNotificationLock") @@ -5835,14 +5835,13 @@ public class NotificationManagerService extends SystemService { mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground)); } - private void onConversationRemovedInternal(String pkg, int uid, String conversationId) { + private void onConversationRemovedInternal(String pkg, int uid, Set<String> shortcuts) { checkCallerIsSystem(); Preconditions.checkStringNotEmpty(pkg); - Preconditions.checkStringNotEmpty(conversationId); - mHistoryManager.deleteConversation(pkg, uid, conversationId); + mHistoryManager.deleteConversations(pkg, uid, shortcuts); List<String> deletedChannelIds = - mPreferencesHelper.deleteConversation(pkg, uid, conversationId); + mPreferencesHelper.deleteConversations(pkg, uid, shortcuts); for (String channelId : deletedChannelIds) { cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true, UserHandle.getUserId(uid), REASON_CHANNEL_BANNED, diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 9cf9545d889a..bdf98f41cbbc 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -79,6 +79,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class PreferencesHelper implements RankingConfig { @@ -1428,7 +1429,8 @@ public class PreferencesHelper implements RankingConfig { } } - public @NonNull List<String> deleteConversation(String pkg, int uid, String conversationId) { + public @NonNull List<String> deleteConversations(String pkg, int uid, + Set<String> conversationIds) { synchronized (mPackagePreferences) { List<String> deletedChannelIds = new ArrayList<>(); PackagePreferences r = getPackagePreferencesLocked(pkg, uid); @@ -1438,7 +1440,8 @@ public class PreferencesHelper implements RankingConfig { int N = r.channels.size(); for (int i = 0; i < N; i++) { final NotificationChannel nc = r.channels.valueAt(i); - if (conversationId.equals(nc.getConversationId())) { + if (nc.getConversationId() != null + && conversationIds.contains(nc.getConversationId())) { nc.setDeleted(true); LogMaker lm = getChannelLog(nc, pkg); lm.setType( diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 60737bf77c48..949dcb254d21 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2081,7 +2081,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mRelinquished = true; - return mPm.new VerificationParams(user, stageDir, localObserver, params, + // TODO(b/169375643): Remove this workaround once b/161121612 is fixed. + PackageInstaller.SessionParams copiedParams = params.copy(); + if (params.isStaged) { + // This is called by the pre-reboot verification. Don't enable rollback here since + // it has been enabled when pre-reboot verification starts. + copiedParams.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK; + } + return mPm.new VerificationParams(user, stageDir, localObserver, copiedParams, mInstallSource, mInstallerUid, mSigningDetails, sessionId); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b451eaf198f8..67f218e5110e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -6214,8 +6214,11 @@ public class PackageManagerService extends IPackageManager.Stub synchronized (mLock) { final AndroidPackage p = mPackages.get(packageName); + if (p == null) { + return false; + } final PackageSetting ps = getPackageSetting(p.getPackageName()); - if (p == null || ps == null) { + if (ps == null) { return false; } final int callingUid = Binder.getCallingUid(); @@ -11291,6 +11294,8 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.addRenamedPackageLPw(parsedPackage.getRealPackage(), originalPkgSetting.name); mTransferredPackages.add(originalPkgSetting.name); + } else { + mSettings.removeRenamedPackageLPw(parsedPackage.getPackageName()); } } if (pkgSetting.sharedUser != null) { @@ -25735,7 +25740,7 @@ public class PackageManagerService extends IPackageManager.Stub // This API is exposed temporarily to only the contacts provider. (b/158688602) final int callingUid = Binder.getCallingUid(); ProviderInfo contactsProvider = resolveContentProviderInternal( - ContactsContract.AUTHORITY, 0, UserHandle.USER_SYSTEM); + ContactsContract.AUTHORITY, 0, UserHandle.getUserId(callingUid)); if (contactsProvider == null || contactsProvider.applicationInfo == null || !UserHandle.isSameApp(contactsProvider.applicationInfo.uid, callingUid)) { throw new SecurityException(callingUid + " is not allow to call grantImplicitAccess"); @@ -25947,6 +25952,15 @@ public class PackageManagerService extends IPackageManager.Stub mPermissionManager.writeStateToPackageSettingsTEMP(); mSettings.writeLPr(); } + + @Override + public void holdLock(int durationMs) { + mContext.enforceCallingPermission( + Manifest.permission.INJECT_EVENTS, "holdLock requires shell identity"); + synchronized (mLock) { + SystemClock.sleep(durationMs); + } + } } interface PackageSender { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index bae36b2ad353..a922d76cf9eb 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -485,6 +485,10 @@ public final class Settings { return mRenamedPackages.put(pkgName, origPkgName); } + void removeRenamedPackageLPw(String pkgName) { + mRenamedPackages.remove(pkgName); + } + public boolean canPropagatePermissionToInstantApp(String permName) { return mPermissions.canPropagatePermissionToInstantApp(permName); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 6b291292d42d..0a8c8f6cbebc 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1729,6 +1729,7 @@ public class UserManagerService extends IUserManager.Stub { } public void makeInitialized(@UserIdInt int userId) { + if (DBG) Slog.d(LOG_TAG, "makeInitialized(" + userId + ")"); checkManageUsersPermission("makeInitialized"); boolean scheduleWriteUser = false; UserData userData; @@ -3553,8 +3554,7 @@ public class UserManagerService extends IUserManager.Stub { // Must start user (which will be stopped right away, through // UserController.finishUserUnlockedCompleted) so services can properly // intialize it. - // TODO(b/143092698): in the long-term, it might be better to add a onCreateUser() - // callback on SystemService instead. + // NOTE: user will be stopped on UserController.finishUserUnlockedCompleted(). Slog.i(LOG_TAG, "starting pre-created user " + userInfo.toFullString()); final IActivityManager am = ActivityManager.getService(); try { @@ -4965,6 +4965,9 @@ public class UserManagerService extends IUserManager.Stub { UserData userData = getUserDataNoChecks(userId); if (userData != null) { writeUserLP(userData); + } else { + Slog.i(LOG_TAG, "handle(WRITE_USER_MSG): no data for user " + userId + + ", it was probably removed before handler could handle it"); } } } diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java index 865b8a1e97eb..eb06bf953059 100644 --- a/services/core/java/com/android/server/pm/permission/BasePermission.java +++ b/services/core/java/com/android/server/pm/permission/BasePermission.java @@ -36,13 +36,14 @@ import android.os.UserHandle; import android.util.Log; import android.util.Slog; -import com.android.internal.util.ArrayUtils; import com.android.server.pm.DumpState; import com.android.server.pm.PackageManagerService; import com.android.server.pm.PackageSettingBase; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.pkg.AndroidPackage; +import libcore.util.EmptyArray; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlSerializer; @@ -95,7 +96,8 @@ public final class BasePermission { int uid; /** Additional GIDs given to apps granted this permission */ - private int[] gids; + @NonNull + private int[] gids = EmptyArray.INT; /** * Flag indicating that {@link #gids} should be adjusted based on the @@ -132,7 +134,7 @@ public final class BasePermission { public int getUid() { return uid; } - public void setGids(int[] gids, boolean perUser) { + public void setGids(@NonNull int[] gids, boolean perUser) { this.gids = gids; this.perUser = perUser; } @@ -141,18 +143,20 @@ public final class BasePermission { } public boolean hasGids() { - return !ArrayUtils.isEmpty(gids); + return gids.length != 0; } + @NonNull public int[] computeGids(int userId) { if (perUser) { final int[] userGids = new int[gids.length]; for (int i = 0; i < gids.length; i++) { - userGids[i] = UserHandle.getUid(userId, gids[i]); + final int gid = gids[i]; + userGids[i] = UserHandle.getUid(userId, gid); } return userGids; } else { - return gids; + return gids.length != 0 ? gids.clone() : gids; } } @@ -206,6 +210,11 @@ public final class BasePermission { return perm != null && (perm.getFlags() & PermissionInfo.FLAG_IMMUTABLY_RESTRICTED) != 0; } + public boolean isInstallerExemptIgnored() { + return perm != null + && (perm.getFlags() & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0; + } + public boolean isSignature() { return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) == PermissionInfo.PROTECTION_SIGNATURE; @@ -286,7 +295,8 @@ public final class BasePermission { pendingPermissionInfo.packageName = newPackageName; } uid = 0; - setGids(null, false); + gids = EmptyArray.INT; + perUser = false; } public boolean addToTree(@ProtectionLevel int protectionLevel, @@ -425,8 +435,7 @@ public final class BasePermission { public void enforceDeclaredUsedAndRuntimeOrDevelopment(AndroidPackage pkg, UidPermissionState uidState) { - int index = pkg.getRequestedPermissions().indexOf(name); - if (!uidState.hasRequestedPermission(name) && index == -1) { + if (!uidState.hasPermissionState(name) && !pkg.getRequestedPermissions().contains(name)) { throw new SecurityException("Package " + pkg.getPackageName() + " has not requested permission " + name); } 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 840b233902f6..b293ba6ccc2d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -24,12 +24,14 @@ import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED; import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; +import static android.content.pm.PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE; import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME; import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; +import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; @@ -53,7 +55,6 @@ import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS; import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; -import static com.android.server.pm.permission.UidPermissionState.PERMISSION_OPERATION_FAILURE; import static java.util.concurrent.TimeUnit.SECONDS; @@ -152,6 +153,8 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal.Permiss import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.SoftRestrictedPermissionPolicy; +import libcore.util.EmptyArray; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -245,6 +248,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { private final SparseArray<ArraySet<String>> mSystemPermissions; /** Built-in group IDs given to all packages. Read from system configuration files. */ + @NonNull private final int[] mGlobalGids; private final HandlerThread mHandlerThread; @@ -785,6 +789,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { throw new IllegalArgumentException("Unknown permission: " + permName); } + if (bp.isInstallerExemptIgnored()) { + flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; + } + final UidPermissionState uidState = getUidState(pkg, userId); if (uidState == null) { Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); @@ -943,7 +951,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { private boolean checkSinglePermissionInternal(int uid, @NonNull UidPermissionState uidState, @NonNull String permissionName) { - if (!uidState.hasPermission(permissionName)) { + if (!uidState.isPermissionGranted(permissionName)) { return false; } @@ -1096,7 +1104,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { Preconditions.checkFlagsArgument(flags, PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER); + | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER + | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE); Preconditions.checkArgumentNonNegative(userId, null); if (UserHandle.getCallingUserId() != userId) { @@ -1120,16 +1129,16 @@ public class PermissionManagerService extends IPermissionManager.Stub { final boolean isCallerInstallerOnRecord = mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); - if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 - && !isCallerPrivileged) { - throw new SecurityException("Querying system whitelist requires " + if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM + | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0 && !isCallerPrivileged) { + throw new SecurityException("Querying system or role allowlist requires " + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); } if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) != 0) { if (!isCallerPrivileged && !isCallerInstallerOnRecord) { - throw new SecurityException("Querying upgrade or installer whitelist" + throw new SecurityException("Querying upgrade or installer allowlist" + " requires being installer on record or " + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); } @@ -1153,6 +1162,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) { queryFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; } + if ((flags & PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE) != 0) { + queryFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; + } ArrayList<String> whitelistedPermissions = null; @@ -1245,7 +1257,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { Preconditions.checkFlagsArgument(flags, PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER); + | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER + | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE); Preconditions.checkArgument(Integer.bitCount(flags) == 1); Preconditions.checkArgumentNonNegative(userId, null); @@ -1271,15 +1284,16 @@ public class PermissionManagerService extends IPermissionManager.Stub { final boolean isCallerInstallerOnRecord = mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); - if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 + if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM + | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0 && !isCallerPrivileged) { - throw new SecurityException("Modifying system whitelist requires " + throw new SecurityException("Modifying system or role allowlist requires " + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); } if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) { if (!isCallerPrivileged && !isCallerInstallerOnRecord) { - throw new SecurityException("Modifying upgrade whitelist requires" + throw new SecurityException("Modifying upgrade allowlist requires" + " being installer on record or " + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); } @@ -1501,7 +1515,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // normal runtime permissions. For now they apply to all users. // TODO(zhanghai): We are breaking the behavior above by making all permission state // per-user. It isn't documented behavior and relatively rarely used anyway. - if (uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) { + if (uidState.grantPermission(bp)) { if (callback != null) { callback.onInstallPermissionGranted(); } @@ -1519,18 +1533,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { return; } - final int result = uidState.grantPermission(bp); - switch (result) { - case PERMISSION_OPERATION_FAILURE: { - return; - } + if (!uidState.grantPermission(bp)) { + return; + } - case UidPermissionState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: { - if (callback != null) { - callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId); - } + if (bp.hasGids()) { + if (callback != null) { + callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId); } - break; } if (bp.isRuntime()) { @@ -1650,7 +1660,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // normal runtime permissions. For now they apply to all users. // TODO(zhanghai): We are breaking the behavior above by making all permission state // per-user. It isn't documented behavior and relatively rarely used anyway. - if (uidState.revokePermission(bp) != PERMISSION_OPERATION_FAILURE) { + if (uidState.revokePermission(bp)) { if (callback != null) { mDefaultPermissionCallback.onInstallPermissionRevoked(); } @@ -1658,12 +1668,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { return; } - // Permission is already revoked, no need to do anything. - if (!uidState.hasPermission(permName)) { - return; - } - - if (uidState.revokePermission(bp) == PERMISSION_OPERATION_FAILURE) { + if (!uidState.revokePermission(bp)) { return; } @@ -2077,6 +2082,15 @@ public class PermissionManagerService extends IPermissionManager.Stub { return false; } + BasePermission permission = getPermission(permName); + if (permission == null) { + return false; + } + if (permission.isHardRestricted() + && (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) { + return false; + } + final long token = Binder.clearCallingIdentity(); try { if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION) @@ -2482,12 +2496,12 @@ public class PermissionManagerService extends IPermissionManager.Stub { return Collections.emptySet(); } if (!ps.getInstantApp(userId)) { - return uidState.getPermissions(); + return uidState.getGrantedPermissions(); } else { // Install permission state is shared among all users, but instant app state is // per-user, so we can only filter it here unless we make install permission state // per-user as well. - final Set<String> instantPermissions = new ArraySet<>(uidState.getPermissions()); + final Set<String> instantPermissions = new ArraySet<>(uidState.getGrantedPermissions()); instantPermissions.removeIf(permissionName -> { BasePermission permission = mSettings.getPermission(permissionName); if (permission == null) { @@ -2504,11 +2518,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - @Nullable + @NonNull private int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) { BasePermission permission = mSettings.getPermission(permissionName); if (permission == null) { - return null; + return EmptyArray.INT; } return permission.computeGids(userId); } @@ -2629,8 +2643,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - uidState.setGlobalGids(mGlobalGids); - ArraySet<String> newImplicitPermissions = new ArraySet<>(); final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")"; @@ -2660,7 +2672,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // Cache newImplicitPermissions before modifing permissionsState as for the shared // uids the original and new state are the same object - if (!origState.hasRequestedPermission(permName) + if (!origState.hasPermissionState(permName) && (pkg.getImplicitPermissions().contains(permName) || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) { if (pkg.getImplicitPermissions().contains(permName)) { @@ -2685,7 +2697,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { SplitPermissionInfoParcelable sp = permissionList.get(splitPermNum); String splitPermName = sp.getSplitPermission(); if (sp.getNewPermissions().contains(permName) - && origState.hasPermission(splitPermName)) { + && origState.isPermissionGranted(splitPermName)) { upgradedActivityRecognitionPermission = splitPermName; newImplicitPermissions.add(permName); @@ -2741,7 +2753,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - if (grant == GRANT_INSTALL && !allowedSig && !origState.hasPermission(perm)) { + if (grant == GRANT_INSTALL && !allowedSig && !origState.isPermissionGranted(perm)) { // If this is an existing, non-system package, then // we can't add any new permissions to it. Runtime // permissions can be added any time - they are dynamic. @@ -2765,7 +2777,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { switch (grant) { case GRANT_INSTALL: { // Grant an install permission. - if (uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) { + if (uidState.grantPermission(bp)) { changedInstallPermission = true; } } break; @@ -2797,8 +2809,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (permissionPolicyInitialized && hardRestricted) { if (!restrictionExempt) { if (origPermState != null && origPermState.isGranted() - && uidState.revokePermission( - bp) != PERMISSION_OPERATION_FAILURE) { + && uidState.revokePermission(bp)) { wasChanged = true; } if (!restrictionApplied) { @@ -2830,8 +2841,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { || (!hardRestricted || restrictionExempt)) { if ((origPermState != null && origPermState.isGranted()) || upgradedActivityRecognitionPermission != null) { - if (uidState.grantPermission(bp) - == PERMISSION_OPERATION_FAILURE) { + if (!uidState.grantPermission(bp)) { wasChanged = true; } } @@ -2849,9 +2859,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - if (!uidState.hasPermission(bp.name) - && uidState.grantPermission(bp) - != PERMISSION_OPERATION_FAILURE) { + if (!uidState.isPermissionGranted(bp.name) + && uidState.grantPermission(bp)) { wasChanged = true; } @@ -2899,13 +2908,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { } break; } } else { - if (uidState.revokePermission(bp) != PERMISSION_OPERATION_FAILURE) { - // Also drop the permission flags. - uidState.updatePermissionFlags(bp, - MASK_PERMISSION_FLAGS_ALL, 0); - changedInstallPermission = true; - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Un-granting permission " + perm + if (DEBUG_PERMISSIONS) { + boolean wasGranted = uidState.isPermissionGranted(bp.name); + if (wasGranted || bp.isAppOp()) { + Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting") + + " permission " + perm + " from package " + friendlyName + " (protectionLevel=" + bp.getProtectionLevel() + " flags=0x" @@ -2913,20 +2920,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { ps)) + ")"); } - } else if (bp.isAppOp()) { - // Don't print warning for app op permissions, since it is fine for them - // not to be granted, there is a UI for the user to decide. - if (DEBUG_PERMISSIONS - && (packageOfInterest == null - || packageOfInterest.equals(pkg.getPackageName()))) { - Slog.i(TAG, "Not granting permission " + perm - + " to package " + friendlyName - + " (protectionLevel=" + bp.getProtectionLevel() - + " flags=0x" - + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, - ps)) - + ")"); - } + } + if (uidState.removePermissionState(bp.name)) { + changedInstallPermission = true; } } } @@ -2993,7 +2989,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { boolean supportsRuntimePermissions = pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M; - for (String permission : ps.getPermissions()) { + for (String permission : ps.getGrantedPermissions()) { if (!pkg.getImplicitPermissions().contains(permission)) { BasePermission bp = mSettings.getPermissionLocked(permission); if (bp.isRuntime()) { @@ -3005,8 +3001,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { if ((flags & BLOCKING_PERMISSION_FLAGS) == 0 && supportsRuntimePermissions) { - int revokeResult = ps.revokePermission(bp); - if (revokeResult != PERMISSION_OPERATION_FAILURE) { + if (ps.revokePermission(bp)) { if (DEBUG_PERMISSIONS) { Slog.i(TAG, "Revoking runtime permission " + permission + " for " + pkgName @@ -3050,7 +3045,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { int numSourcePerm = sourcePerms.size(); for (int i = 0; i < numSourcePerm; i++) { String sourcePerm = sourcePerms.valueAt(i); - if (ps.hasPermission(sourcePerm)) { + if (ps.isPermissionGranted(sourcePerm)) { if (!isGranted) { flags = 0; } @@ -3155,7 +3150,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); - if (!origPs.hasRequestedPermission(sourcePerms)) { + if (!origPs.hasPermissionState(sourcePerms)) { boolean inheritsFromInstallPerm = false; for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size(); sourcePermNum++) { @@ -3465,7 +3460,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (!allowed && bp.isDevelopment()) { // For development permissions, a development permission // is granted only if it was already granted. - allowed = origPermissions.hasPermission(perm); + allowed = origPermissions.isPermissionGranted(perm); } if (!allowed && bp.isSetup() && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( @@ -3687,7 +3682,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { continue; } - if (uidState.hasPermission(permissionName)) { + if (uidState.isPermissionGranted(permissionName)) { if (oldGrantedRestrictedPermissions.get(userId) == null) { oldGrantedRestrictedPermissions.put(userId, new ArraySet<>()); } @@ -3730,6 +3725,15 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } break; + case FLAG_PERMISSION_ALLOWLIST_ROLE: { + mask |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; + if (permissions != null && permissions.contains(permissionName)) { + newFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; + } else { + newFlags &= ~FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; + } + } + break; } } @@ -3749,7 +3753,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // as whitelisting trumps policy i.e. policy cannot grant a non // grantable permission. if ((oldFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) { - final boolean isGranted = uidState.hasPermission(permissionName); + final boolean isGranted = uidState.isPermissionGranted(permissionName); if (!isWhitelisted && isGranted) { mask |= PackageManager.FLAG_PERMISSION_POLICY_FIXED; newFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED; @@ -3791,7 +3795,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { + " and user " + userId); continue; } - if (!newUidState.hasPermission(permission)) { + if (!newUidState.isPermissionGranted(permission)) { callback.onPermissionRevoked(pkg.getUid(), userId, null); break; } @@ -3865,14 +3869,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - // The package is gone - no need to keep flags for applying policy. - uidState.updatePermissionFlags(bp, PackageManager.MASK_PERMISSION_FLAGS_ALL, 0); - - // Try to revoke as a runtime permission which is per user. - // TODO(zhanghai): This doesn't make sense. revokePermission() doesn't fail, and why are - // we only killing the uid when gids changed, instead of any permission change? - if (uidState.revokePermission(bp) - == UidPermissionState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) { + // TODO(zhanghai): Why are we only killing the UID when GIDs changed, instead of any + // permission change? + if (uidState.removePermissionState(bp.name) && bp.hasGids()) { affectedUserId = userId; } } @@ -3905,17 +3904,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { boolean runtimePermissionChanged = false; // Prune permissions - final List<com.android.server.pm.permission.PermissionState> permissionStates = - uidState.getPermissionStates(); + final List<PermissionState> permissionStates = uidState.getPermissionStates(); final int permissionStatesSize = permissionStates.size(); for (int i = permissionStatesSize - 1; i >= 0; i--) { PermissionState permissionState = permissionStates.get(i); if (!usedPermissions.contains(permissionState.getName())) { BasePermission bp = mSettings.getPermissionLocked(permissionState.getName()); if (bp != null) { - uidState.revokePermission(bp); - uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, 0); - if (permissionState.isRuntime()) { + if (uidState.removePermissionState(bp.name) && permissionState.isRuntime()) { runtimePermissionChanged = true; } } @@ -4178,11 +4174,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { + p.getPackageName() + " and user " + userId); return; } - if (uidState.getPermissionState(bp.getName()) != null) { - uidState.revokePermission(bp); - uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, - 0); - } + uidState.removePermissionState(bp.name); } }); } @@ -4741,7 +4733,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID " + userId); return EMPTY_INT_ARRAY; } - return uidState.computeGids(userId); + return uidState.computeGids(mGlobalGids, userId); } private class PermissionManagerServiceInternalImpl extends PermissionManagerServiceInternal { @@ -4804,7 +4796,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { @UserIdInt int userId) { return PermissionManagerService.this.getGrantedPermissions(packageName, userId); } - @Nullable + @NonNull @Override public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) { return PermissionManagerService.this.getPermissionGids(permissionName, userId); diff --git a/services/core/java/com/android/server/pm/permission/PermissionState.java b/services/core/java/com/android/server/pm/permission/PermissionState.java index 38264c83a15c..59b204f7dfff 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionState.java +++ b/services/core/java/com/android/server/pm/permission/PermissionState.java @@ -17,7 +17,6 @@ package com.android.server.pm.permission; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.UserIdInt; import com.android.internal.annotations.GuardedBy; @@ -62,7 +61,7 @@ public final class PermissionState { return mPermission.getName(); } - @Nullable + @NonNull public int[] computeGids(@UserIdInt int userId) { return mPermission.computeGids(userId); } diff --git a/services/core/java/com/android/server/pm/permission/UidPermissionState.java b/services/core/java/com/android/server/pm/permission/UidPermissionState.java index b45176b720b5..c73e2f3e153b 100644 --- a/services/core/java/com/android/server/pm/permission/UidPermissionState.java +++ b/services/core/java/com/android/server/pm/permission/UidPermissionState.java @@ -22,151 +22,55 @@ import android.annotation.UserIdInt; import android.content.pm.PackageManager; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.IntArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.ArrayUtils; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; /** * Permission state for a UID. - * <p> - * This class is also responsible for keeping track of the Linux GIDs per - * user for a package or a shared user. The GIDs are computed as a set of - * the GIDs for all granted permissions' GIDs on a per user basis. */ public final class UidPermissionState { - /** The permission operation failed. */ - public static final int PERMISSION_OPERATION_FAILURE = -1; - - /** The permission operation succeeded and no gids changed. */ - public static final int PERMISSION_OPERATION_SUCCESS = 0; - - /** The permission operation succeeded and gids changed. */ - public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 1; - - private static final int[] NO_GIDS = {}; - @NonNull private final Object mLock = new Object(); - @GuardedBy("mLock") - private ArrayMap<String, PermissionState> mPermissions; - - @NonNull - private int[] mGlobalGids = NO_GIDS; - private boolean mMissing; - private boolean mPermissionReviewRequired; - - public UidPermissionState() { - /* do nothing */ - } - - public UidPermissionState(@NonNull UidPermissionState prototype) { - copyFrom(prototype); - } - - /** - * Gets the global gids, applicable to all users. - */ - @NonNull - public int[] getGlobalGids() { - return mGlobalGids; - } - - /** - * Sets the global gids, applicable to all users. - * - * @param globalGids The global gids. - */ - public void setGlobalGids(@NonNull int[] globalGids) { - if (!ArrayUtils.isEmpty(globalGids)) { - mGlobalGids = Arrays.copyOf(globalGids, globalGids.length); - } - } + @GuardedBy("mLock") + @Nullable + private ArrayMap<String, PermissionState> mPermissions; - static void invalidateCache() { - PackageManager.invalidatePackageInfoCache(); - } - - /** - * Initialized this instance from another one. - * - * @param other The other instance. - */ - public void copyFrom(@NonNull UidPermissionState other) { - if (other == this) { - return; - } + public UidPermissionState() {} + public UidPermissionState(@NonNull UidPermissionState other) { synchronized (mLock) { - if (mPermissions != null) { - if (other.mPermissions == null) { - mPermissions = null; - } else { - mPermissions.clear(); - } - } + mMissing = other.mMissing; + if (other.mPermissions != null) { - if (mPermissions == null) { - mPermissions = new ArrayMap<>(); - } - final int permissionCount = other.mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - String name = other.mPermissions.keyAt(i); - PermissionState permissionState = other.mPermissions.valueAt(i); + mPermissions = new ArrayMap<>(); + final int permissionsSize = other.mPermissions.size(); + for (int i = 0; i < permissionsSize; i++) { + final String name = other.mPermissions.keyAt(i); + final PermissionState permissionState = other.mPermissions.valueAt(i); mPermissions.put(name, new PermissionState(permissionState)); } } } - - mGlobalGids = NO_GIDS; - if (other.mGlobalGids != NO_GIDS) { - mGlobalGids = other.mGlobalGids.clone(); - } - - mMissing = other.mMissing; - - mPermissionReviewRequired = other.mPermissionReviewRequired; } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final UidPermissionState other = (UidPermissionState) obj; - + /** + * Reset the internal state of this object. + */ + public void reset() { synchronized (mLock) { - if (mPermissions == null) { - if (other.mPermissions != null) { - return false; - } - } else if (!mPermissions.equals(other.mPermissions)) { - return false; - } - } - - if (mMissing != other.mMissing) { - return false; - } - - if (mPermissionReviewRequired != other.mPermissionReviewRequired) { - return false; + mMissing = false; + mPermissions = null; + invalidateCache(); } - return Arrays.equals(mGlobalGids, other.mGlobalGids); } /** @@ -186,366 +90,294 @@ public final class UidPermissionState { mMissing = missing; } - public boolean isPermissionReviewRequired() { - return mPermissionReviewRequired; - } - /** - * Gets whether the state has a given permission. + * Get whether there is a permission state for a permission. * - * @param name The permission name. - * @return Whether the state has the permission. + * @deprecated This used to be named hasRequestedPermission() and its usage is confusing */ - public boolean hasPermission(@NonNull String name) { + @Deprecated + public boolean hasPermissionState(@NonNull String name) { synchronized (mLock) { - if (mPermissions == null) { - return false; - } - PermissionState permissionState = mPermissions.get(name); - return permissionState != null && permissionState.isGranted(); + return mPermissions != null && mPermissions.containsKey(name); } } /** - * Returns whether the state has any known request for the given permission name, - * whether or not it has been granted. + * Get whether there is a permission state for any of the permissions. * - * @deprecated Not all requested permissions may be here. + * @deprecated This used to be named hasRequestedPermission() and its usage is confusing */ @Deprecated - public boolean hasRequestedPermission(@NonNull ArraySet<String> names) { + public boolean hasPermissionState(@NonNull ArraySet<String> names) { synchronized (mLock) { if (mPermissions == null) { return false; } - for (int i = names.size() - 1; i >= 0; i--) { - if (mPermissions.get(names.valueAt(i)) != null) { + final int namesSize = names.size(); + for (int i = 0; i < namesSize; i++) { + final String name = names.valueAt(i); + if (mPermissions.containsKey(name)) { return true; } } + return false; } - - return false; } /** - * Returns whether the state has any known request for the given permission name, - * whether or not it has been granted. + * Gets the state for a permission or null if none. * - * @deprecated Not all requested permissions may be here. + * @param name the permission name + * @return the permission state */ - @Deprecated - public boolean hasRequestedPermission(@NonNull String name) { - return mPermissions != null && (mPermissions.get(name) != null); + @Nullable + public PermissionState getPermissionState(@NonNull String name) { + synchronized (mLock) { + if (mPermissions == null) { + return null; + } + return mPermissions.get(name); + } } - /** - * Gets all permissions for a given device user id regardless if they - * are install time or runtime permissions. - * - * @return The permissions or an empty set. - */ @NonNull - public Set<String> getPermissions() { + private PermissionState getOrCreatePermissionState(@NonNull BasePermission permission) { synchronized (mLock) { if (mPermissions == null) { - return Collections.emptySet(); + mPermissions = new ArrayMap<>(); } - - Set<String> permissions = new ArraySet<>(mPermissions.size()); - - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - String permission = mPermissions.keyAt(i); - - if (hasPermission(permission)) { - permissions.add(permission); - } + final String name = permission.getName(); + PermissionState permissionState = mPermissions.get(name); + if (permissionState == null) { + permissionState = new PermissionState(permission); + mPermissions.put(name, permissionState); } - - return permissions; + return permissionState; } } /** - * Gets the flags for a permission. + * Get all permission states. * - * @param name The permission name. - * @return The permission state or null if no such. + * @return the permission states */ - public int getPermissionFlags(@NonNull String name) { - PermissionState permState = getPermissionState(name); - if (permState != null) { - return permState.getFlags(); + @NonNull + public List<PermissionState> getPermissionStates() { + synchronized (mLock) { + if (mPermissions == null) { + return Collections.emptyList(); + } + return new ArrayList<>(mPermissions.values()); } - return 0; } /** - * Update the flags associated with a given permission. - * @param permission The permission whose flags to update. - * @param flagMask Mask for which flags to change. - * @param flagValues New values for the mask flags. - * @return Whether the permission flags changed. + * Put a permission state. + * + * @param permission the permission + * @param granted whether the permission is granted + * @param flags the permission flags */ - public boolean updatePermissionFlags(@NonNull BasePermission permission, int flagMask, - int flagValues) { - if (flagMask == 0) { - return false; - } - - PermissionState permissionState = ensurePermissionState(permission); - - final int oldFlags = permissionState.getFlags(); - + public void putPermissionState(@NonNull BasePermission permission, boolean granted, int flags) { synchronized (mLock) { - final boolean updated = permissionState.updateFlags(flagMask, flagValues); - if (updated) { - final int newFlags = permissionState.getFlags(); - if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0 - && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { - mPermissionReviewRequired = true; - } else if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0 - && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0) { - if (mPermissionReviewRequired && !hasPermissionRequiringReview()) { - mPermissionReviewRequired = false; - } - } + final String name = permission.getName(); + if (mPermissions == null) { + mPermissions = new ArrayMap<>(); + } else { + mPermissions.remove(name); } - return updated; - } - } - - private boolean hasPermissionRequiringReview() { - synchronized (mLock) { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - final PermissionState permission = mPermissions.valueAt(i); - if ((permission.getFlags() & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { - return true; - } + final PermissionState permissionState = new PermissionState(permission); + if (granted) { + permissionState.grant(); } + permissionState.updateFlags(flags, flags); + mPermissions.put(name, permissionState); } - return false; } - public boolean updatePermissionFlagsForAllPermissions(int flagMask, int flagValues) { + /** + * Remove a permission state. + * + * @param name the permission name + * @return whether the permission state changed + */ + public boolean removePermissionState(@NonNull String name) { synchronized (mLock) { if (mPermissions == null) { return false; } - boolean changed = false; - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - PermissionState permissionState = mPermissions.valueAt(i); - changed |= permissionState.updateFlags(flagMask, flagValues); + boolean changed = mPermissions.remove(name) != null; + if (changed && mPermissions.isEmpty()) { + mPermissions = null; } return changed; } } /** - * Compute the Linux gids for a given device user from the permissions - * granted to this user. Note that these are computed to avoid additional - * state as they are rarely accessed. + * Get whether a permission is granted. * - * @param userId The device user id. - * @return The gids for the device user. + * @param name the permission name + * @return whether the permission is granted */ - @NonNull - public int[] computeGids(@UserIdInt int userId) { - int[] gids = mGlobalGids; - - synchronized (mLock) { - if (mPermissions != null) { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - PermissionState permissionState = mPermissions.valueAt(i); - if (!permissionState.isGranted()) { - continue; - } - final int[] permGids = permissionState.computeGids(userId); - if (permGids != NO_GIDS) { - gids = appendInts(gids, permGids); - } - } - } - } - - return gids; + public boolean isPermissionGranted(@NonNull String name) { + final PermissionState permissionState = getPermissionState(name); + return permissionState != null && permissionState.isGranted(); } /** - * Compute the Linux gids for all device users from the permissions - * granted to these users. + * Get all the granted permissions. * - * @return The gids for all device users. + * @return the granted permissions */ @NonNull - public int[] computeGids(@NonNull int[] userIds) { - int[] gids = mGlobalGids; - - for (int userId : userIds) { - final int[] userGids = computeGids(userId); - gids = appendInts(gids, userGids); - } - - return gids; - } - - /** - * Resets the internal state of this object. - */ - public void reset() { - mGlobalGids = NO_GIDS; - + public Set<String> getGrantedPermissions() { synchronized (mLock) { - mPermissions = null; - invalidateCache(); - } + if (mPermissions == null) { + return Collections.emptySet(); + } - mMissing = false; - mPermissionReviewRequired = false; - } + Set<String> permissions = new ArraySet<>(mPermissions.size()); + final int permissionsSize = mPermissions.size(); + for (int i = 0; i < permissionsSize; i++) { + PermissionState permissionState = mPermissions.valueAt(i); - /** - * Gets the state for a permission or null if no such. - * - * @param name The permission name. - * @return The permission state. - */ - @Nullable - public PermissionState getPermissionState(@NonNull String name) { - synchronized (mLock) { - if (mPermissions == null) { - return null; + if (permissionState.isGranted()) { + permissions.add(permissionState.getName()); + } } - return mPermissions.get(name); + return permissions; } } /** - * Gets all permission states. + * Grant a permission. * - * @return The permission states or an empty set. + * @param permission the permission to grant + * @return whether the permission grant state changed */ - @NonNull - public List<PermissionState> getPermissionStates() { - synchronized (mLock) { - if (mPermissions == null) { - return Collections.emptyList(); - } - return new ArrayList<>(mPermissions.values()); - } + public boolean grantPermission(@NonNull BasePermission permission) { + PermissionState permissionState = getOrCreatePermissionState(permission); + return permissionState.grant(); } /** - * Put a permission state. + * Revoke a permission. + * + * @param permission the permission to revoke + * @return whether the permission grant state changed */ - public void putPermissionState(@NonNull BasePermission permission, boolean isGranted, - int flags) { - synchronized (mLock) { - ensureNoPermissionState(permission.name); - PermissionState permissionState = ensurePermissionState(permission); - if (isGranted) { - permissionState.grant(); - } - permissionState.updateFlags(flags, flags); - if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { - mPermissionReviewRequired = true; - } + public boolean revokePermission(@NonNull BasePermission permission) { + final String name = permission.getName(); + final PermissionState permissionState = getPermissionState(name); + if (permissionState == null) { + return false; + } + final boolean changed = permissionState.revoke(); + if (changed && permissionState.isDefault()) { + removePermissionState(name); } + return changed; } /** - * Grant a permission. + * Get the flags for a permission. * - * @param permission The permission to grant. - * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, - * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link - * #PERMISSION_OPERATION_FAILURE}. + * @param name the permission name + * @return the permission flags */ - public int grantPermission(@NonNull BasePermission permission) { - if (hasPermission(permission.getName())) { - return PERMISSION_OPERATION_SUCCESS; - } - - PermissionState permissionState = ensurePermissionState(permission); - - if (!permissionState.grant()) { - return PERMISSION_OPERATION_FAILURE; + public int getPermissionFlags(@NonNull String name) { + final PermissionState permissionState = getPermissionState(name); + if (permissionState == null) { + return 0; } - - return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED - : PERMISSION_OPERATION_SUCCESS; + return permissionState.getFlags(); } /** - * Revoke a permission. + * Update the flags for a permission. * - * @param permission The permission to revoke. - * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, - * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link - * #PERMISSION_OPERATION_FAILURE}. + * @param permission the permission name + * @param flagMask the mask for the flags + * @param flagValues the new values for the masked flags + * @return whether the permission flags changed */ - public int revokePermission(@NonNull BasePermission permission) { - final String permissionName = permission.getName(); - if (!hasPermission(permissionName)) { - return PERMISSION_OPERATION_SUCCESS; + public boolean updatePermissionFlags(@NonNull BasePermission permission, int flagMask, + int flagValues) { + if (flagMask == 0) { + return false; } - - PermissionState permissionState; - synchronized (mLock) { - permissionState = mPermissions.get(permissionName); + final PermissionState permissionState = getOrCreatePermissionState(permission); + final boolean changed = permissionState.updateFlags(flagMask, flagValues); + if (changed && permissionState.isDefault()) { + removePermissionState(permission.name); } + return changed; + } - if (!permissionState.revoke()) { - return PERMISSION_OPERATION_FAILURE; + public boolean updatePermissionFlagsForAllPermissions(int flagMask, int flagValues) { + if (flagMask == 0) { + return false; } - - if (permissionState.isDefault()) { - ensureNoPermissionState(permissionName); + synchronized (mLock) { + if (mPermissions == null) { + return false; + } + boolean anyChanged = false; + for (int i = mPermissions.size() - 1; i >= 0; i--) { + final PermissionState permissionState = mPermissions.valueAt(i); + final boolean changed = permissionState.updateFlags(flagMask, flagValues); + if (changed && permissionState.isDefault()) { + mPermissions.removeAt(i); + } + anyChanged |= changed; + } + return anyChanged; } - - return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED - : PERMISSION_OPERATION_SUCCESS; } - // TODO: fix this to use arraycopy and append all ints in one go - private static int[] appendInts(int[] current, int[] added) { - if (current != null && added != null) { - for (int guid : added) { - current = ArrayUtils.appendInt(current, guid); + public boolean isPermissionReviewRequired() { + synchronized (mLock) { + final int permissionsSize = mPermissions.size(); + for (int i = 0; i < permissionsSize; i++) { + final PermissionState permission = mPermissions.valueAt(i); + if ((permission.getFlags() & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { + return true; + } } + return false; } - return current; } + /** + * Compute the Linux GIDs from the permissions granted to a user. + * + * @param userId the user ID + * @return the GIDs for the user + */ @NonNull - private PermissionState ensurePermissionState(@NonNull BasePermission permission) { - final String permissionName = permission.getName(); + public int[] computeGids(@NonNull int[] globalGids, @UserIdInt int userId) { synchronized (mLock) { + IntArray gids = IntArray.wrap(globalGids); if (mPermissions == null) { - mPermissions = new ArrayMap<>(); + return gids.toArray(); } - PermissionState permissionState = mPermissions.get(permissionName); - if (permissionState == null) { - permissionState = new PermissionState(permission); - mPermissions.put(permissionName, permissionState); + final int permissionsSize = mPermissions.size(); + for (int i = 0; i < permissionsSize; i++) { + PermissionState permissionState = mPermissions.valueAt(i); + if (!permissionState.isGranted()) { + continue; + } + final int[] permissionGids = permissionState.computeGids(userId); + if (permissionGids.length != 0) { + gids.addAll(permissionGids); + } } - return permissionState; + return gids.toArray(); } } - private void ensureNoPermissionState(@NonNull String name) { - synchronized (mLock) { - if (mPermissions == null) { - return; - } - mPermissions.remove(name); - if (mPermissions.isEmpty()) { - mPermissions = null; - } - } + static void invalidateCache() { + PackageManager.invalidatePackageInfoCache(); } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 35b14490eba2..df283e210be8 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1552,6 +1552,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { startActivityAsUser(intent, UserHandle.CURRENT); } + private void toggleNotificationPanel() { + IStatusBarService statusBarService = getStatusBarService(); + if (statusBarService != null) { + try { + statusBarService.togglePanel(); + } catch (RemoteException e) { + // do nothing. + } + } + } + private void showPictureInPictureMenu(KeyEvent event) { if (DEBUG_INPUT) Log.d(TAG, "showPictureInPictureMenu event=" + event); mHandler.removeMessages(MSG_SHOW_PICTURE_IN_PICTURE_MENU); @@ -1696,14 +1707,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { launchAssistAction(null, deviceId); break; case LONG_PRESS_HOME_NOTIFICATION_PANEL: - IStatusBarService statusBarService = getStatusBarService(); - if (statusBarService != null) { - try { - statusBarService.togglePanel(); - } catch (RemoteException e) { - // do nothing. - } - } + toggleNotificationPanel(); break; default: Log.w(TAG, "Undefined long press on home behavior: " @@ -2807,6 +2811,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { msg.sendToTarget(); } return -1; + } else if (keyCode == KeyEvent.KEYCODE_NOTIFICATION) { + if (!down) { + toggleNotificationPanel(); + } + return -1; } // Toggle Caps Lock on META-ALT. diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 6044ee18614a..60da8e5c7b70 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -5408,6 +5408,22 @@ public final class PowerManagerService extends SystemService } @Override // Binder call + public boolean isAmbientDisplaySuppressedForTokenByApp(@NonNull String token, int appUid) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_DREAM_STATE, null); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_DREAM_SUPPRESSION, null); + + final long ident = Binder.clearCallingIdentity(); + try { + return isAmbientDisplayAvailable() + && mAmbientDisplaySuppressionController.isSuppressed(token, appUid); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call public boolean isAmbientDisplaySuppressed() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.READ_DREAM_STATE, null); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 874b910e54f2..d1fd0adb44c2 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -56,6 +56,7 @@ import android.view.WindowInsetsController.Appearance; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.SoftInputShowHideReason; +import com.android.internal.os.TransferPipe; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; @@ -1510,6 +1511,23 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D return mContext.getResources().getStringArray(R.array.config_statusBarIcons); } + /** @hide */ + public void passThroughShellCommand(String[] args, FileDescriptor fd) { + enforceStatusBarOrShell(); + if (mBar == null) return; + + try (TransferPipe tp = new TransferPipe()) { + // Sending the command to the remote, which needs to execute async to avoid blocking + // See Binder#dumpAsync() for inspiration + tp.setBufferPrefix(" "); + mBar.passThroughShellCommand(args, tp.getWriteFd()); + // Times out after 5s + tp.go(fd); + } catch (Throwable t) { + Slog.e(TAG, "Error sending command to IStatusBar", t); + } + } + // ================================================================================ // Can be called from any thread // ================================================================================ diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java index a79c19f7bc17..617182290d39 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java +++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java @@ -46,7 +46,8 @@ public class StatusBarShellCommand extends ShellCommand { @Override public int onCommand(String cmd) { if (cmd == null) { - return handleDefaultCommands(cmd); + onHelp(); + return 1; } try { switch (cmd) { @@ -74,8 +75,16 @@ public class StatusBarShellCommand extends ShellCommand { return runSendDisableFlag(); case "tracing": return runTracing(); + // Handle everything that would be handled in `handleDefaultCommand()` explicitly, + // so the default can be to pass all args to StatusBar + case "-h": + case "help": + onHelp(); + return 0; + case "dump": + return super.handleDefaultCommands(cmd); default: - return handleDefaultCommands(cmd); + return runPassArgsToStatusBar(); } } catch (RemoteException e) { final PrintWriter pw = getOutPrintWriter(); @@ -187,6 +196,11 @@ public class StatusBarShellCommand extends ShellCommand { return 0; } + private int runPassArgsToStatusBar() { + mInterface.passThroughShellCommand(getAllArgs(), getOutFileDescriptor()); + return 0; + } + private int runTracing() { switch (getNextArg()) { case "start": @@ -250,6 +264,12 @@ public class StatusBarShellCommand extends ShellCommand { pw.println(" tracing (start | stop)"); pw.println(" Start or stop SystemUI tracing"); pw.println(""); + pw.println(" NOTE: any command not listed here will be passed through to IStatusBar"); + pw.println(""); + pw.println(" Commands implemented in SystemUI:"); + pw.flush(); + // Sending null args to systemui will print help + mInterface.passThroughShellCommand(new String[] {}, getOutFileDescriptor()); } /** diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 3406bd99c883..70ab48b9005b 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -84,6 +84,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { contentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true, new ContentObserver(handler) { + @Override public void onChange(boolean selfChange) { timeDetectorService.handleAutoTimeDetectionChanged(); } diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index fe0e82e66093..9c18aadb79ec 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -572,7 +572,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { */ @VisibleForTesting @Nullable - public NetworkTimeSuggestion findLatestValidNetworkSuggestionForTests() { + public synchronized NetworkTimeSuggestion findLatestValidNetworkSuggestionForTests() { return findLatestValidNetworkSuggestion(); } @@ -590,7 +590,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { */ @VisibleForTesting @Nullable - public NetworkTimeSuggestion getLatestNetworkSuggestion() { + public synchronized NetworkTimeSuggestion getLatestNetworkSuggestion() { return mLastNetworkSuggestion.get(); } diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java index b68c54fc6365..9e76bc19360f 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java @@ -16,15 +16,16 @@ package com.android.server.timezonedetector; -import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED; -import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE; -import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED; -import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED; import android.annotation.NonNull; import android.annotation.UserIdInt; -import android.app.timezonedetector.TimeZoneCapabilities; -import android.app.timezonedetector.TimeZoneConfiguration; +import android.app.time.TimeZoneCapabilities; +import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneConfiguration; import android.os.UserHandle; import java.util.Objects; @@ -32,7 +33,7 @@ import java.util.Objects; /** * Holds all configuration values that affect time zone behavior and some associated logic, e.g. * {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link - * #createCapabilities()}. + * #createCapabilitiesAndConfig()}. */ public final class ConfigurationInternal { @@ -106,10 +107,15 @@ public final class ConfigurationInternal { return false; } - /** Creates a {@link TimeZoneCapabilities} object using the configuration values. */ - public TimeZoneCapabilities createCapabilities() { - TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder() - .setConfiguration(asConfiguration()); + /** Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values. */ + public TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig() { + return new TimeZoneCapabilitiesAndConfig(asCapabilities(), asConfiguration()); + } + + @NonNull + private TimeZoneCapabilities asCapabilities() { + UserHandle userHandle = UserHandle.of(mUserId); + TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle); boolean allowConfigDateTime = isUserConfigAllowed(); @@ -125,7 +131,7 @@ public final class ConfigurationInternal { } else { configureAutoDetectionEnabledCapability = CAPABILITY_POSSESSED; } - builder.setConfigureAutoDetectionEnabled(configureAutoDetectionEnabledCapability); + builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability); final int configureGeolocationDetectionEnabledCapability; if (!deviceHasTimeZoneDetection) { @@ -137,7 +143,8 @@ public final class ConfigurationInternal { } else { configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED; } - builder.setConfigureGeoDetectionEnabled(configureGeolocationDetectionEnabledCapability); + builder.setConfigureGeoDetectionEnabledCapability( + configureGeolocationDetectionEnabledCapability); // The ability to make manual time zone suggestions can also be restricted by policy. With // the current logic above, this could lead to a situation where a device hardware does not @@ -151,14 +158,14 @@ public final class ConfigurationInternal { } else { suggestManualTimeZoneCapability = CAPABILITY_POSSESSED; } - builder.setSuggestManualTimeZone(suggestManualTimeZoneCapability); + builder.setSuggestManualTimeZoneCapability(suggestManualTimeZoneCapability); return builder.build(); } /** Returns a {@link TimeZoneConfiguration} from the configuration values. */ - public TimeZoneConfiguration asConfiguration() { - return new TimeZoneConfiguration.Builder(mUserId) + private TimeZoneConfiguration asConfiguration() { + return new TimeZoneConfiguration.Builder() .setAutoDetectionEnabled(getAutoDetectionEnabledSetting()) .setGeoDetectionEnabled(getGeoDetectionEnabledSetting()) .build(); @@ -171,10 +178,10 @@ public final class ConfigurationInternal { */ public ConfigurationInternal merge(TimeZoneConfiguration newConfiguration) { Builder builder = new Builder(this); - if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED)) { + if (newConfiguration.hasIsAutoDetectionEnabled()) { builder.setAutoDetectionEnabled(newConfiguration.isAutoDetectionEnabled()); } - if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED)) { + if (newConfiguration.hasIsGeoDetectionEnabled()) { builder.setGeoDetectionEnabled(newConfiguration.isGeoDetectionEnabled()); } return builder.build(); diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java index 6a12b7c8f9a5..964dbecf705c 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java @@ -23,7 +23,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.app.AlarmManager; -import android.app.timezonedetector.TimeZoneConfiguration; +import android.app.time.TimeZoneConfiguration; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -87,6 +87,7 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat contentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true, new ContentObserver(mHandler) { + @Override public void onChange(boolean selfChange) { handleConfigChangeOnHandlerThread(); } @@ -97,6 +98,7 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat Settings.Secure.getUriFor(Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED), true, new ContentObserver(mHandler) { + @Override public void onChange(boolean selfChange) { handleConfigChangeOnHandlerThread(); } @@ -157,7 +159,7 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat } @Override - public void storeConfiguration(TimeZoneConfiguration configuration) { + public void storeConfiguration(@UserIdInt int userId, TimeZoneConfiguration configuration) { Objects.requireNonNull(configuration); // Avoid writing the auto detection enabled setting for devices that do not support auto @@ -169,7 +171,6 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat setAutoDetectionEnabled(autoDetectionEnabled); if (mGeoDetectionFeatureEnabled) { - final int userId = configuration.getUserId(); final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled(); setGeoDetectionEnabled(userId, geoTzDetectionEnabled); } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index 73322a6987df..6e1f89b3919d 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -18,18 +18,19 @@ package com.android.server.timezonedetector; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.timezonedetector.ITimeZoneConfigurationListener; +import android.app.time.ITimeZoneDetectorListener; +import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneConfiguration; import android.app.timezonedetector.ITimeZoneDetectorService; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; -import android.app.timezonedetector.TimeZoneCapabilities; -import android.app.timezonedetector.TimeZoneConfiguration; import android.content.Context; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; +import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -41,7 +42,6 @@ import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Objects; /** @@ -109,10 +109,14 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub @NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy; - @GuardedBy("mConfigurationListeners") + /** + * Holds the listeners. The key is the {@link IBinder} associated with the listener, the value + * is the listener itself. + */ + @GuardedBy("mListeners") @NonNull - private final ArrayList<ITimeZoneConfigurationListener> mConfigurationListeners = - new ArrayList<>(); + private final ArrayMap<IBinder, ITimeZoneDetectorListener> mListeners = + new ArrayMap<>(); private static TimeZoneDetectorService create( @NonNull Context context, @NonNull Handler handler, @@ -133,20 +137,22 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector); mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy); - // Wire up a change listener so that ITimeZoneConfigurationListeners can be notified when + // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when // the configuration changes for any reason. mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged); } @Override @NonNull - public TimeZoneCapabilities getCapabilities() { - enforceManageTimeZoneDetectorConfigurationPermission(); + public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig() { + enforceManageTimeZoneDetectorPermission(); int userId = mCallerIdentityInjector.getCallingUserId(); long token = mCallerIdentityInjector.clearCallingIdentity(); try { - return mTimeZoneDetectorStrategy.getConfigurationInternal(userId).createCapabilities(); + ConfigurationInternal configurationInternal = + mTimeZoneDetectorStrategy.getConfigurationInternal(userId); + return configurationInternal.createCapabilitiesAndConfig(); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } @@ -154,37 +160,34 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub @Override public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) { - enforceManageTimeZoneDetectorConfigurationPermission(); + enforceManageTimeZoneDetectorPermission(); Objects.requireNonNull(configuration); int callingUserId = mCallerIdentityInjector.getCallingUserId(); - if (callingUserId != configuration.getUserId()) { - return false; - } - long token = mCallerIdentityInjector.clearCallingIdentity(); try { - return mTimeZoneDetectorStrategy.updateConfiguration(configuration); + return mTimeZoneDetectorStrategy.updateConfiguration(callingUserId, configuration); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } } @Override - public void addConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) { - enforceManageTimeZoneDetectorConfigurationPermission(); + public void addListener(@NonNull ITimeZoneDetectorListener listener) { + enforceManageTimeZoneDetectorPermission(); Objects.requireNonNull(listener); - synchronized (mConfigurationListeners) { - if (mConfigurationListeners.contains(listener)) { + synchronized (mListeners) { + IBinder listenerBinder = listener.asBinder(); + if (mListeners.containsKey(listenerBinder)) { return; } try { // Ensure the reference to the listener will be removed if the client process dies. - listener.asBinder().linkToDeath(this, 0 /* flags */); + listenerBinder.linkToDeath(this, 0 /* flags */); // Only add the listener if we can linkToDeath(). - mConfigurationListeners.add(listener); + mListeners.put(listenerBinder, listener); } catch (RemoteException e) { Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e); } @@ -192,21 +195,22 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub } @Override - public void removeConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) { - enforceManageTimeZoneDetectorConfigurationPermission(); + public void removeListener(@NonNull ITimeZoneDetectorListener listener) { + enforceManageTimeZoneDetectorPermission(); Objects.requireNonNull(listener); - synchronized (mConfigurationListeners) { + synchronized (mListeners) { + IBinder listenerBinder = listener.asBinder(); boolean removedListener = false; - if (mConfigurationListeners.remove(listener)) { + if (mListeners.remove(listenerBinder) != null) { // Stop listening for the client process to die. - listener.asBinder().unlinkToDeath(this, 0 /* flags */); + listenerBinder.unlinkToDeath(this, 0 /* flags */); removedListener = true; } if (!removedListener) { Slog.w(TAG, "Client asked to remove listener=" + listener + ", but no listeners were removed." - + " mConfigurationListeners=" + mConfigurationListeners); + + " mListeners=" + mListeners); } } } @@ -218,19 +222,18 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub } /** - * Called when one of the ITimeZoneConfigurationListener processes dies before calling - * {@link #removeConfigurationListener(ITimeZoneConfigurationListener)}. + * Called when one of the ITimeZoneDetectorListener processes dies before calling + * {@link #removeListener(ITimeZoneDetectorListener)}. */ @Override public void binderDied(IBinder who) { - synchronized (mConfigurationListeners) { + synchronized (mListeners) { boolean removedListener = false; - final int listenerCount = mConfigurationListeners.size(); + final int listenerCount = mListeners.size(); for (int listenerIndex = listenerCount - 1; listenerIndex >= 0; listenerIndex--) { - ITimeZoneConfigurationListener listener = - mConfigurationListeners.get(listenerIndex); - if (listener.asBinder().equals(who)) { - mConfigurationListeners.remove(listenerIndex); + IBinder listenerBinder = mListeners.keyAt(listenerIndex); + if (listenerBinder.equals(who)) { + mListeners.removeAt(listenerIndex); removedListener = true; break; } @@ -238,7 +241,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub if (!removedListener) { Slog.w(TAG, "Notified of binder death for who=" + who + ", but did not remove any listeners." - + " mConfigurationListeners=" + mConfigurationListeners); + + " mConfigurationListeners=" + mListeners); } } } @@ -247,11 +250,10 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub // Configuration has changed, but each user may have a different view of the configuration. // It's possible that this will cause unnecessary notifications but that shouldn't be a // problem. - synchronized (mConfigurationListeners) { - final int listenerCount = mConfigurationListeners.size(); + synchronized (mListeners) { + final int listenerCount = mListeners.size(); for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) { - ITimeZoneConfigurationListener listener = - mConfigurationListeners.get(listenerIndex); + ITimeZoneDetectorListener listener = mListeners.valueAt(listenerIndex); try { listener.onChange(); } catch (RemoteException e) { @@ -302,11 +304,10 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub ipw.flush(); } - private void enforceManageTimeZoneDetectorConfigurationPermission() { - // TODO Switch to a dedicated MANAGE_TIME_AND_ZONE_CONFIGURATION permission. + private void enforceManageTimeZoneDetectorPermission() { mContext.enforceCallingPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS, - "manage time and time zone configuration"); + android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION, + "manage time and time zone detection"); } private void enforceSuggestGeolocationTimeZonePermission() { @@ -333,7 +334,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { - (new TimeZoneDetectorShellCommand(this)).exec( + new TimeZoneDetectorShellCommand(this).exec( this, in, out, err, args, callback, resultReceiver); } } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index f944c5638fa9..0b1d6d71ea7b 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -17,9 +17,9 @@ package com.android.server.timezonedetector; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.time.TimeZoneConfiguration; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; -import android.app.timezonedetector.TimeZoneConfiguration; import android.util.IndentingPrintWriter; /** @@ -96,7 +96,8 @@ public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container { * <p>This method returns {@code true} if the configuration was changed, * {@code false} otherwise. */ - boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration); + boolean updateConfiguration( + @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration); /** * Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index 8a42b18b514f..781668bebbc9 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -15,20 +15,21 @@ */ package com.android.server.timezonedetector; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE; -import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.time.TimeZoneCapabilities; +import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneConfiguration; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; -import android.app.timezonedetector.TimeZoneCapabilities; -import android.app.timezonedetector.TimeZoneConfiguration; import android.content.Context; import android.os.Handler; import android.util.IndentingPrintWriter; @@ -93,7 +94,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * All checks about user capabilities must be done by the caller and * {@link TimeZoneConfiguration#isComplete()} must be {@code true}. */ - void storeConfiguration(TimeZoneConfiguration newConfiguration); + void storeConfiguration(@UserIdInt int userId, TimeZoneConfiguration newConfiguration); } private static final String LOG_TAG = "TimeZoneDetectorStrategy"; @@ -240,27 +241,26 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat } @Override - public synchronized boolean updateConfiguration( + public synchronized boolean updateConfiguration(@UserIdInt int userId, @NonNull TimeZoneConfiguration requestedConfiguration) { Objects.requireNonNull(requestedConfiguration); - int userId = requestedConfiguration.getUserId(); - TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities(); + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + getConfigurationInternal(userId).createCapabilitiesAndConfig(); + TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + TimeZoneConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration(); - // Create a new configuration builder, and copy across the mutable properties users are - // able to modify. Other properties are therefore ignored. final TimeZoneConfiguration newConfiguration = - capabilities.applyUpdate(requestedConfiguration); + capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration); if (newConfiguration == null) { - // The changes could not be made due to + // The changes could not be made because the user's capabilities do not allow it. return false; } // Store the configuration / notify as needed. This will cause the mCallback to invoke // handleConfigChanged() asynchronously. - mCallback.storeConfiguration(newConfiguration); + mCallback.storeConfiguration(userId, newConfiguration); - TimeZoneConfiguration oldConfiguration = capabilities.getConfiguration(); String logMsg = "Configuration changed:" + " oldConfiguration=" + oldConfiguration + ", newConfiguration=" + newConfiguration; @@ -313,8 +313,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat String timeZoneId = suggestion.getZoneId(); String cause = "Manual time suggestion received: suggestion=" + suggestion; - TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities(); - if (capabilities.getSuggestManualTimeZone() != CAPABILITY_POSSESSED) { + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + getConfigurationInternal(userId).createCapabilitiesAndConfig(); + TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + if (capabilities.getSuggestManualTimeZoneCapability() != CAPABILITY_POSSESSED) { Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually" + ", capabilities=" + capabilities + ", timeZoneId=" + timeZoneId @@ -605,7 +607,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat ipw.println("mCallback.getCurrentUserId()=" + currentUserId); ConfigurationInternal configuration = mCallback.getConfigurationInternal(currentUserId); ipw.println("mCallback.getConfiguration(currentUserId)=" + configuration); - ipw.println("[Capabilities=" + configuration.createCapabilities() + "]"); + ipw.println("[Capabilities=" + configuration.createCapabilitiesAndConfig() + "]"); ipw.println("mCallback.isDeviceTimeZoneInitialized()=" + mCallback.isDeviceTimeZoneInitialized()); ipw.println("mCallback.getDeviceTimeZone()=" + mCallback.getDeviceTimeZone()); @@ -644,7 +646,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * A method used to inspect strategy state during tests. Not intended for general use. */ @VisibleForTesting - public GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() { + public synchronized GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() { return mLatestGeoLocationSuggestion.get(); } @@ -652,7 +654,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata. */ @VisibleForTesting - public static class QualifiedTelephonyTimeZoneSuggestion { + public static final class QualifiedTelephonyTimeZoneSuggestion { @VisibleForTesting public final TelephonyTimeZoneSuggestion suggestion; diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 8ccbbdd2265c..cfceaabfc229 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -22,7 +22,9 @@ import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -33,6 +35,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; @@ -51,6 +54,7 @@ import android.media.tv.ITvInputService; import android.media.tv.ITvInputServiceCallback; import android.media.tv.ITvInputSession; import android.media.tv.ITvInputSessionCallback; +import android.media.tv.TvChannelInfo; import android.media.tv.TvContentRating; import android.media.tv.TvContentRatingSystemInfo; import android.media.tv.TvContract; @@ -78,6 +82,7 @@ import android.util.SparseArray; import android.view.InputChannel; import android.view.Surface; +import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; @@ -108,6 +113,9 @@ public final class TvInputManagerService extends SystemService { private static final boolean DEBUG = false; private static final String TAG = "TvInputManagerService"; private static final String DVB_DIRECTORY = "/dev/dvb"; + private static final int APP_TAG_SELF = TvChannelInfo.APP_TAG_SELF; + private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS = + "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"; // There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d, // another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the @@ -136,6 +144,8 @@ public final class TvInputManagerService extends SystemService { private final WatchLogHandler mWatchLogHandler; + private final ActivityManager mActivityManager; + public TvInputManagerService(Context context) { super(context); @@ -144,6 +154,9 @@ public final class TvInputManagerService extends SystemService { IoThread.get().getLooper()); mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener()); + mActivityManager = + (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE); + synchronized (mLock) { getOrCreateUserStateLocked(mCurrentUserId); } @@ -686,14 +699,18 @@ public final class TvInputManagerService extends SystemService { SessionState sessionState = null; try { sessionState = getSessionStateLocked(sessionToken, callingUid, userId); + UserState userState = getOrCreateUserStateLocked(userId); if (sessionState.session != null) { - UserState userState = getOrCreateUserStateLocked(userId); if (sessionToken == userState.mainSessionToken) { setMainLocked(sessionToken, false, callingUid, userId); } sessionState.session.asBinder().unlinkToDeath(sessionState, 0); sessionState.session.release(); } + sessionState.isCurrent = false; + sessionState.currentChannel = null; + notifyCurrentChannelInfosUpdatedLocked( + userState, getCurrentTvChannelInfosInternalLocked(userState)); } catch (RemoteException | SessionNotFoundException e) { Slog.e(TAG, "error in releaseSession", e); } finally { @@ -834,6 +851,22 @@ public final class TvInputManagerService extends SystemService { } } + private void notifyCurrentChannelInfosUpdatedLocked( + UserState userState, List<TvChannelInfo> infos) { + if (DEBUG) { + Slog.d(TAG, "notifyCurrentChannelInfosUpdatedLocked"); + } + int n = userState.mCallbacks.beginBroadcast(); + for (int i = 0; i < n; ++i) { + try { + userState.mCallbacks.getBroadcastItem(i).onCurrentTvChannelInfosUpdated(infos); + } catch (RemoteException e) { + Slog.e(TAG, "failed to report updated current channel infos to callback", e); + } + } + userState.mCallbacks.finishBroadcast(); + } + private void updateTvInputInfoLocked(UserState userState, TvInputInfo inputInfo) { if (DEBUG) { Slog.d(TAG, "updateTvInputInfoLocked(inputInfo=" + inputInfo + ")"); @@ -1394,13 +1427,19 @@ public final class TvInputManagerService extends SystemService { try { getSessionLocked(sessionToken, callingUid, resolvedUserId).tune( channelUri, params); + UserState userState = getOrCreateUserStateLocked(resolvedUserId); + SessionState sessionState = userState.sessionStateMap.get(sessionToken); + if (sessionState != null) { + sessionState.isCurrent = true; + sessionState.currentChannel = channelUri; + notifyCurrentChannelInfosUpdatedLocked( + userState, getCurrentTvChannelInfosInternalLocked(userState)); + } if (TvContract.isChannelUriForPassthroughInput(channelUri)) { // Do not log the watch history for passthrough inputs. return; } - UserState userState = getOrCreateUserStateLocked(resolvedUserId); - SessionState sessionState = userState.sessionStateMap.get(sessionToken); if (sessionState.isRecordingSession) { return; } @@ -2049,6 +2088,21 @@ public final class TvInputManagerService extends SystemService { return clientPid; } + @Override + public List<TvChannelInfo> getCurrentTvChannelInfos(@UserIdInt int userId) { + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), + Binder.getCallingUid(), userId, "getTvCurrentChannelInfos"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + UserState userState = getOrCreateUserStateLocked(resolvedUserId); + return getCurrentTvChannelInfosInternalLocked(userState); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + /** * Add a hardware device in the TvInputHardwareManager for CTS testing * purpose. @@ -2219,6 +2273,69 @@ public final class TvInputManagerService extends SystemService { } } + private List<TvChannelInfo> getCurrentTvChannelInfosInternalLocked(UserState userState) { + List<TvChannelInfo> channelInfos = new ArrayList<>(); + boolean watchedProgramsAccess = hasAccessWatchedProgramsPermission(); + for (SessionState state : userState.sessionStateMap.values()) { + if (state.isCurrent) { + Integer appTag; + int appType; + if (state.callingUid == Binder.getCallingUid()) { + appTag = APP_TAG_SELF; + appType = TvChannelInfo.APP_TYPE_SELF; + } else { + appTag = userState.mAppTagMap.get(state.callingUid); + if (appTag == null) { + appTag = userState.mNextAppTag++; + userState.mAppTagMap.put(state.callingUid, appTag); + } + appType = isSystemApp(state.componentName.getPackageName()) + ? TvChannelInfo.APP_TYPE_SYSTEM + : TvChannelInfo.APP_TYPE_NON_SYSTEM; + } + channelInfos.add(new TvChannelInfo( + state.inputId, + watchedProgramsAccess ? state.currentChannel : null, + state.isRecordingSession, + isForeground(state.callingPid), + appType, + appTag)); + } + } + return channelInfos; + } + + private boolean isForeground(int pid) { + if (mActivityManager == null) { + return false; + } + List<RunningAppProcessInfo> appProcesses = mActivityManager.getRunningAppProcesses(); + if (appProcesses == null) { + return false; + } + for (RunningAppProcessInfo appProcess : appProcesses) { + if (appProcess.pid == pid + && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + return true; + } + } + return false; + } + + private boolean hasAccessWatchedProgramsPermission() { + return mContext.checkCallingPermission(PERMISSION_ACCESS_WATCHED_PROGRAMS) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean isSystemApp(String pkg) { + try { + return (mContext.getPackageManager().getApplicationInfo(pkg, 0).flags + & ApplicationInfo.FLAG_SYSTEM) != 0; + } catch (NameNotFoundException e) { + return false; + } + } + private static final class UserState { // A mapping from the TV input id to its TvInputState. private Map<String, TvInputState> inputMap = new HashMap<>(); @@ -2250,6 +2367,11 @@ public final class TvInputManagerService extends SystemService { // service. private final PersistentDataStore persistentDataStore; + @GuardedBy("mLock") + private final Map<Integer, Integer> mAppTagMap = new HashMap<>(); + @GuardedBy("mLock") + private int mNextAppTag = 1; + private UserState(Context context, int userId) { persistentDataStore = new PersistentDataStore(context, userId); } @@ -2336,6 +2458,9 @@ public final class TvInputManagerService extends SystemService { // Not null if this session represents an external device connected to a hardware TV input. private IBinder hardwareSessionToken; + private boolean isCurrent = false; + private Uri currentChannel = null; + private SessionState(IBinder sessionToken, String inputId, ComponentName componentName, boolean isRecordingSession, ITvInputClient client, int seq, int callingUid, int callingPid, int userId, String sessionId) { @@ -2584,6 +2709,11 @@ public final class TvInputManagerService extends SystemService { if (mSessionState.session == null || mSessionState.client == null) { return; } + mSessionState.isCurrent = true; + mSessionState.currentChannel = channelUri; + UserState userState = getOrCreateUserStateLocked(mSessionState.userId); + notifyCurrentChannelInfosUpdatedLocked( + userState, getCurrentTvChannelInfosInternalLocked(userState)); try { // TODO: Consider adding this channel change in the watch log. When we do // that, how we can protect the watch log from malicious tv inputs should diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index 39f6c597508c..f6acc64bdaa0 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -115,7 +115,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { private static final String TAG = "UriGrantsManagerService"; // Maximum number of persisted Uri grants a package is allowed private static final int MAX_PERSISTED_URI_GRANTS = 512; - private static final boolean ENABLE_DYNAMIC_PERMISSIONS = false; + private static final boolean ENABLE_DYNAMIC_PERMISSIONS = true; private final Object mLock = new Object(); private final H mH; diff --git a/services/core/java/com/android/server/utils/XmlName.java b/services/core/java/com/android/server/utils/XmlName.java new file mode 100644 index 000000000000..c0e22daaa32e --- /dev/null +++ b/services/core/java/com/android/server/utils/XmlName.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.utils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify the XML name for the annotated field or persistence class. + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ ElementType.FIELD, ElementType.TYPE }) +public @interface XmlName { + String value(); +} diff --git a/libs/hwui/shader/RuntimeShader.h b/services/core/java/com/android/server/utils/XmlPersistence.java index 7fe0b0206467..8900a9d4783e 100644 --- a/libs/hwui/shader/RuntimeShader.h +++ b/services/core/java/com/android/server/utils/XmlPersistence.java @@ -14,27 +14,23 @@ * limitations under the License. */ -#pragma once +package com.android.server.utils; -#include "Shader.h" -#include "SkShader.h" -#include "include/effects/SkRuntimeEffect.h" - -namespace android::uirenderer { +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** - * RuntimeShader implementation that can map to either a SkShader or SkImageFilter + * Generate XML persistence for the annotated class. */ -class RuntimeShader : public Shader { -public: - RuntimeShader(SkRuntimeEffect& effect, sk_sp<SkData> data, bool isOpaque, - const SkMatrix* matrix); - ~RuntimeShader() override; - -protected: - sk_sp<SkShader> makeSkShader() override; - -private: - sk_sp<SkShader> skShader; -}; -} // namespace android::uirenderer +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface XmlPersistence { + /** + * Name for the generated XML persistence class. + * <p> + * The name defaults to the target class name appended with {@code Persistence} if unspecified. + */ + String value(); +} diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 67924307f009..2355ed3272ea 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1308,6 +1308,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } + // TODO(b/169035022): move to a more-appropriate place. + mAtmService.getTransitionController().collect(this); if (prevDc.mOpeningApps.remove(this)) { // Transfer opening transition to new display. mDisplayContent.mOpeningApps.add(this); @@ -2700,7 +2702,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // activities be updated, they may be seen by users. ensureVisibility = true; } else if (mStackSupervisor.getKeyguardController().isKeyguardLocked() - && stack.topActivityOccludesKeyguard()) { + && mStackSupervisor.getKeyguardController().topActivityOccludesKeyguard(this)) { // Ensure activity visibilities and update lockscreen occluded/dismiss state when // finishing the top activity that occluded keyguard. So that, the // ActivityStack#mTopActivityOccludesKeyguard can be updated and the activity below @@ -3234,6 +3236,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mStackSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this); waitingToShow = false; + // TODO(b/169035022): move to a more-appropriate place. + mAtmService.getTransitionController().collect(this); // Defer removal of this activity when either a child is animating, or app transition is on // going. App transition animation might be applied on the parent stack not on the activity, // but the actual frame buffer is associated with the activity, so we have to keep the @@ -3245,6 +3249,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (getDisplayContent().mAppTransition.isTransitionSet()) { getDisplayContent().mClosingApps.add(this); delayed = true; + } else if (mAtmService.getTransitionController().inTransition()) { + delayed = true; } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, @@ -4076,6 +4082,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mVisible; } + @Override + boolean isVisibleRequested() { + return mVisibleRequested; + } + void setVisible(boolean visible) { if (visible != mVisible) { mVisible = visible; @@ -4206,6 +4217,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A transferStartingWindowFromHiddenAboveTokenIfNeeded(); } + // TODO(b/169035022): move to a more-appropriate place. + mAtmService.getTransitionController().collect(this); + if (!visible && mAtmService.getTransitionController().inTransition()) { + return; + } // If we are preparing an app transition, then delay changing // the visibility of this token until we execute that transition. // Note that we ignore display frozen since we want the opening / closing transition type @@ -4465,6 +4481,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } detachChildren(); } + if (app != null) { + app.invalidateOomScoreReferenceState(false /* computeNow */); + } switch (state) { case RESUMED: @@ -4628,15 +4647,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(this); } - private void updateVisibleIgnoringKeyguard(boolean behindFullscreenActivity) { - // Check whether activity should be visible without Keyguard influence - visibleIgnoringKeyguard = (!behindFullscreenActivity || mLaunchTaskBehind) - && okToShowLocked(); - } - /** @return {@code true} if this activity should be made visible. */ private boolean shouldBeVisible(boolean behindFullscreenActivity, boolean ignoringKeyguard) { - updateVisibleIgnoringKeyguard(behindFullscreenActivity); + updateVisibilityIgnoringKeyguard(behindFullscreenActivity); if (ignoringKeyguard) { return visibleIgnoringKeyguard; @@ -4670,20 +4683,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mStackSupervisor.getKeyguardController().checkKeyguardVisibility(this); } - void updateVisibility(boolean behindFullscreenActivity) { - updateVisibleIgnoringKeyguard(behindFullscreenActivity); - final Task task = getRootTask(); - if (task == null || !visibleIgnoringKeyguard) { - return; - } - // Now check whether it's really visible depending on Keyguard state, and update - // {@link ActivityStack} internal states. - // Inform the method if this activity is the top activity of this stack, but exclude the - // case where this is the top activity in a pinned stack. - final boolean isTop = this == task.getTopNonFinishingActivity(); - final boolean isTopNotPinnedStack = task.isAttached() - && task.getDisplayArea().isTopNotFinishNotPinnedStack(task); - task.updateKeyguardVisibility(this, isTop && isTopNotPinnedStack); + void updateVisibilityIgnoringKeyguard(boolean behindFullscreenActivity) { + visibleIgnoringKeyguard = (!behindFullscreenActivity || mLaunchTaskBehind) + && okToShowLocked(); } boolean shouldBeVisible() { @@ -7864,7 +7866,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A InsetUtils.addInsets(insets, getLetterboxInsets()); return new RemoteAnimationTarget(task.mTaskId, record.getMode(), record.mAdapter.mCapturedLeash, !fillsParent(), - mainWindow.mWinAnimator.mLastClipRect, insets, + new Rect(), insets, getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mLocalBounds, record.mAdapter.mStackBounds, task.getWindowConfiguration(), false /*isNotInRecents*/, diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index f7fd6ec1fbc6..3ef383b2eed7 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -1830,7 +1830,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { for (int i = mStoppingActivities.size() - 1; i >= 0; --i) { final ActivityRecord s = mStoppingActivities.get(i); final boolean animating = s.isAnimating(TRANSITION | PARENTS, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS); + ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS) + || mService.getTransitionController().inTransition(s); if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible + " animating=" + animating + " finishing=" + s.finishing); if (!animating || mService.mShuttingDown) { @@ -2304,18 +2305,14 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { /** Starts a batch of visibility updates. */ void beginActivityVisibilityUpdate() { + if (mVisibilityTransactionDepth == 0) { + getKeyguardController().updateVisibility(); + } mVisibilityTransactionDepth++; } /** Ends a batch of visibility updates. */ - void endActivityVisibilityUpdate(ActivityRecord starting, int configChanges, - boolean preserveWindows, boolean notifyClients) { - if (mVisibilityTransactionDepth == 1) { - getKeyguardController().visibilitiesUpdated(); - // commit visibility to activities - mRootWindowContainer.commitActivitiesVisible(starting, configChanges, preserveWindows, - notifyClients); - } + void endActivityVisibilityUpdate() { mVisibilityTransactionDepth--; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 2dc22ecfc022..e65be41f44cd 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -499,9 +499,6 @@ public abstract class ActivityTaskManagerInternal { /** @return the process for the top-most resumed activity in the system. */ public abstract WindowProcessController getTopApp(); - /** Generate oom-score-adjustment rank for all tasks in the system based on z-order. */ - public abstract void rankTaskLayersIfNeeded(); - /** Destroy all activities. */ public abstract void scheduleDestroyAllActivities(String reason); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 15669cbe227a..c0e31f90b0ae 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -666,7 +666,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { */ CompatModePackages mCompatModePackages; - private FontScaleSettingObserver mFontScaleSettingObserver; + private SettingObserver mSettingsObserver; WindowOrganizerController mWindowOrganizerController; TaskOrganizerController mTaskOrganizerController; @@ -676,16 +676,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private int mDeviceOwnerUid = Process.INVALID_UID; - private final class FontScaleSettingObserver extends ContentObserver { + private final class SettingObserver extends ContentObserver { private final Uri mFontScaleUri = Settings.System.getUriFor(FONT_SCALE); private final Uri mHideErrorDialogsUri = Settings.Global.getUriFor(HIDE_ERROR_DIALOGS); + private final Uri mForceBoldTextUri = Settings.Secure.getUriFor( + Settings.Secure.FORCE_BOLD_TEXT); - public FontScaleSettingObserver() { + SettingObserver() { super(mH); final ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(mFontScaleUri, false, this, UserHandle.USER_ALL); resolver.registerContentObserver(mHideErrorDialogsUri, false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(mForceBoldTextUri, false, this, UserHandle.USER_ALL); } @Override @@ -698,6 +701,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { synchronized (mGlobalLock) { updateShouldShowDialogsLocked(getGlobalConfiguration()); } + } else if (mForceBoldTextUri.equals(uri)) { + updateForceBoldTextIfNeeded(userId); } } } @@ -757,7 +762,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } public void installSystemProviders() { - mFontScaleSettingObserver = new FontScaleSettingObserver(); + mSettingsObserver = new SettingObserver(); } public void retrieveSettings(ContentResolver resolver) { @@ -953,6 +958,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mLockTaskController; } + TransitionController getTransitionController() { + return mWindowOrganizerController.getTransitionController(); + } + /** * Return the global configuration used by the process corresponding to the input pid. This is * usually the global configuration with some overrides specific to that process. @@ -5362,6 +5371,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + private void updateForceBoldTextIfNeeded(@UserIdInt int userId) { + final int forceBoldTextConfig = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.FORCE_BOLD_TEXT, Configuration.FORCE_BOLD_TEXT_UNDEFINED, userId); + synchronized (mGlobalLock) { + if (getGlobalConfiguration().forceBoldText == forceBoldTextConfig) { + return; + } + final Configuration configuration = + mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY); + configuration.forceBoldText = forceBoldTextConfig; + updatePersistentConfiguration(configuration, userId); + } + } + // Actually is sleeping or shutting down or whatever else in the future // is an inactive state. boolean isSleepingOrShuttingDownLocked() { @@ -7187,16 +7210,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - @HotPath(caller = HotPath.OOM_ADJUSTMENT) - @Override - public void rankTaskLayersIfNeeded() { - synchronized (mGlobalLockWithoutBoost) { - if (mRootWindowContainer != null) { - mRootWindowContainer.rankTaskLayersIfNeeded(); - } - } - } - @Override public void scheduleDestroyAllActivities(String reason) { synchronized (mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index f76108f332d1..0e47ea8058f1 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -2214,6 +2214,10 @@ public class AppTransition implements Dump { */ boolean prepareAppTransitionLocked(@TransitionType int transit, boolean alwaysKeepCurrent, @TransitionFlags int flags, boolean forceOverride) { + if (mService.mAtmService.getTransitionController().adaptLegacyPrepare( + transit, flags, forceOverride)) { + return false; + } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Prepare app transition: transit=%s %s alwaysKeepCurrent=%b displayId=%d " + "Callers=%s", @@ -2255,7 +2259,7 @@ public class AppTransition implements Dump { || transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER; } - private static boolean isKeyguardTransit(int transit) { + static boolean isKeyguardTransit(int transit) { return isKeyguardGoingAwayTransit(transit) || transit == TRANSIT_KEYGUARD_OCCLUDE || transit == TRANSIT_KEYGUARD_UNOCCLUDE; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index b3e69d4847f7..22b446d32ecc 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -193,7 +193,6 @@ import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.SurfaceSession; -import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerPolicyConstants.PointerEventListener; @@ -552,11 +551,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private SurfaceControl mParentSurfaceControl; private InputWindowHandle mPortalWindowHandle; - // Last systemUiVisibility we received from status bar. - private int mLastStatusBarVisibility = 0; - // Last systemUiVisibility we dispatched to windows. - private int mLastDispatchedSystemUiVisibility = 0; - /** Corner radius that windows should have in order to match the display. */ private final float mWindowCornerRadius; @@ -1471,6 +1465,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // intermediate orientation change, it is more stable to freeze the display. return false; } + if (r.isState(RESUMED) && !r.getRootTask().mInResumeTopActivity) { + // If the activity is executing or has done the lifecycle callback, use normal + // rotation animation so the display info can be updated immediately (see + // updateDisplayAndOrientation). This prevents a compatibility issue such as + // calling setRequestedOrientation in Activity#onCreate and then get display info. + // If fixed rotation is applied, the display rotation will still be the old one, + // unless the client side gets the rotation again after the adjustments arrive. + return false; + } } else if (r != topRunningActivity()) { // If the transition has not started yet, the activity must be the top. return false; @@ -2279,6 +2282,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } @Override + boolean isVisibleRequested() { + return isVisible(); + } + + @Override void onAppTransitionDone() { super.onAppTransitionDone(); mWmService.mWindowsChanged = true; @@ -2907,10 +2915,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp pw.print(" mLastFocus="); pw.println(mLastFocus); } pw.print(" mFocusedApp="); pw.println(mFocusedApp); - if (mLastStatusBarVisibility != 0) { - pw.print(" mLastStatusBarVisibility=0x"); - pw.println(Integer.toHexString(mLastStatusBarVisibility)); - } if (mFixedRotationLaunchingApp != null) { pw.println(" mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp); } @@ -3770,50 +3774,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return win != null; } - void hideTransientBars() { - // TODO(b/118118435): Remove this after migration - final int transientFlags = View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT; - statusBarVisibilityChanged(mLastStatusBarVisibility & ~transientFlags); - - getInsetsPolicy().hideTransient(); - } - - void statusBarVisibilityChanged(int visibility) { - mLastStatusBarVisibility = visibility; - updateStatusBarVisibilityLocked(visibility); - } - - private boolean updateStatusBarVisibilityLocked(int visibility) { - if (mLastDispatchedSystemUiVisibility == visibility) { - return false; - } - final int globalDiff = (visibility ^ mLastDispatchedSystemUiVisibility) - // We are only interested in differences of one of the - // clearable flags... - & View.SYSTEM_UI_CLEARABLE_FLAGS - // ...if it has actually been cleared. - & ~visibility; - - mLastDispatchedSystemUiVisibility = visibility; - if (isDefaultDisplay) { - mWmService.mInputManager.setSystemUiVisibility(visibility); - } - updateSystemUiVisibility(visibility, globalDiff); - return true; - } - - void updateSystemUiVisibility(int visibility, int globalDiff) { - forAllWindows(w -> { - final int curValue = w.mSystemUiVisibility; - final int diff = (curValue ^ visibility) & globalDiff; - final int newValue = (curValue & ~diff) | (visibility & diff); - if (newValue != curValue) { - w.mSeq++; - w.mSystemUiVisibility = newValue; - } - }, true /* traverseTopToBottom */); - } - void onWindowFreezeTimeout() { Slog.w(TAG_WM, "Window freeze timeout expired."); mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT; @@ -4486,6 +4446,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void executeAppTransition() { + mAtmService.getTransitionController().setReady(); if (mAppTransition.isTransitionSet()) { ProtoLog.w(WM_DEBUG_APP_TRANSITIONS, "Execute app transition: %s, displayId: %d Callers=%s", diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index e54da9e99852..dce798e81a86 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -43,6 +43,7 @@ import static android.view.InsetsState.ITYPE_TOP_GESTURES; import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; +import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE; @@ -156,6 +157,7 @@ import android.view.MotionEvent; import android.view.PointerIcon; import android.view.Surface; import android.view.View; +import android.view.ViewDebug; import android.view.WindowInsets.Side; import android.view.WindowInsets.Side.InsetsSide; import android.view.WindowInsets.Type; @@ -1445,9 +1447,9 @@ public class DisplayPolicy { boolean getLayoutHint(LayoutParams attrs, WindowToken windowToken, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout) { - final int fl = PolicyControl.getWindowFlags(null, attrs); + final int fl = attrs.flags; final int pfl = attrs.privateFlags; - final int sysUiVis = PolicyControl.getSystemUiVisibility(null, attrs); + final int sysUiVis = attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility; final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) != 0; final boolean layoutInScreenAndInsetDecor = layoutInScreen @@ -2001,7 +2003,7 @@ public class DisplayPolicy { final WindowManager.LayoutParams attrs = win.getAttrs(); final int type = attrs.type; - final int fl = PolicyControl.getWindowFlags(win, attrs); + final int fl = attrs.flags; final int pfl = attrs.privateFlags; final int sim = attrs.softInputMode; @@ -2243,7 +2245,7 @@ public class DisplayPolicy { final boolean affectsSystemUi = win.canAffectSystemUiFlags(); if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": affectsSystemUi=" + affectsSystemUi); mService.mPolicy.applyKeyguardPolicyLw(win, imeTarget); - final int fl = PolicyControl.getWindowFlags(win, attrs); + final int fl = attrs.flags; if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi && attrs.type == TYPE_INPUT_METHOD) { mForcingShowNavBar = true; @@ -2418,8 +2420,7 @@ public class DisplayPolicy { return false; } final LayoutParams attrs = mTopFullscreenOpaqueWindowState.getAttrs(); - final int fl = PolicyControl.getWindowFlags(null, attrs); - final int sysui = PolicyControl.getSystemUiVisibility(null, attrs); + final int fl = attrs.flags; final InsetsSource request = mTopFullscreenOpaqueWindowState.getRequestedInsetsState() .peekSource(ITYPE_STATUS_BAR); if (WindowManagerDebugConfig.DEBUG) { @@ -2952,9 +2953,9 @@ public class DisplayPolicy { mDisplayContent.getInsetsPolicy().updateBarControlTarget(win); - final int fullscreenAppearance = updateLightStatusBarLw(0 /* vis */, + final int fullscreenAppearance = updateLightStatusBarLw(0 /* appearance */, mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState); - final int dockedAppearance = updateLightStatusBarLw(0 /* vis */, + final int dockedAppearance = updateLightStatusBarLw(0 /* appearance */, mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState); final boolean inSplitScreen = mService.mRoot.getDefaultTaskDisplayArea().isSplitScreenModeActivated(); @@ -3025,6 +3026,10 @@ public class DisplayPolicy { } }); + if (mDisplayContent.isDefaultDisplay) { + mService.mInputManager.setSystemUiLightsOut( + isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0); + } return true; } @@ -3037,9 +3042,7 @@ public class DisplayPolicy { // If the top fullscreen-or-dimming window is also the top fullscreen, respect // its light flag. appearance &= ~APPEARANCE_LIGHT_STATUS_BARS; - final int legacyAppearance = InsetsFlags.getAppearance( - PolicyControl.getSystemUiVisibility(statusColorWin, null)); - appearance |= (statusColorWin.mAttrs.insetsFlags.appearance | legacyAppearance) + appearance |= statusColorWin.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS; } else if (statusColorWin.isDimming()) { // Otherwise if it's dimming, clear the light flag. @@ -3062,8 +3065,8 @@ public class DisplayPolicy { final boolean imeWindowCanNavColorWindow = imeWindow != null && imeWindow.isVisibleLw() && navBarPosition == NAV_BAR_BOTTOM - && (PolicyControl.getWindowFlags(imeWindow, null) - & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; + && (imeWindow.mAttrs.flags + & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; if (opaque != null && opaqueOrDimming == opaque) { // If the top fullscreen-or-dimming window is also the top fullscreen, respect it @@ -3085,7 +3088,7 @@ public class DisplayPolicy { // The IME window and the dimming window are competing. Check if the dimming window can be // IME target or not. - if (LayoutParams.mayUseInputMethod(PolicyControl.getWindowFlags(opaqueOrDimming, null))) { + if (LayoutParams.mayUseInputMethod(opaqueOrDimming.mAttrs.flags)) { // The IME window is above the dimming window. return imeWindow; } else { @@ -3359,22 +3362,30 @@ public class DisplayPolicy { pw.print(prefix); pw.print("mHdmiPlugged="); pw.println(mHdmiPlugged); if (mLastDisableFlags != 0) { pw.print(prefix); pw.print("mLastDisableFlags=0x"); - pw.print(Integer.toHexString(mLastDisableFlags)); + pw.println(Integer.toHexString(mLastDisableFlags)); + } + if (mLastAppearance != 0) { + pw.print(prefix); pw.print("mLastAppearance="); + pw.println(ViewDebug.flagsToString(InsetsFlags.class, "appearance", mLastAppearance)); + } + if (mLastBehavior != 0) { + pw.print(prefix); pw.print("mLastBehavior="); + pw.println(ViewDebug.flagsToString(InsetsFlags.class, "behavior", mLastBehavior)); } pw.print(prefix); pw.print("mShowingDream="); pw.print(mShowingDream); - pw.print(" mDreamingLockscreen="); pw.print(mDreamingLockscreen); + pw.print(" mDreamingLockscreen="); pw.println(mDreamingLockscreen); if (mStatusBar != null) { - pw.print(prefix); pw.print("mStatusBar="); pw.print(mStatusBar); + pw.print(prefix); pw.print("mStatusBar="); pw.println(mStatusBar); } if (mStatusBarAlt != null) { - pw.print(prefix); pw.print("mStatusBarAlt="); pw.print(mStatusBarAlt); + pw.print(prefix); pw.print("mStatusBarAlt="); pw.println(mStatusBarAlt); pw.print(prefix); pw.print("mStatusBarAltPosition="); pw.println(mStatusBarAltPosition); } if (mNotificationShade != null) { - pw.print(prefix); pw.print("mExpandedPanel="); pw.print(mNotificationShade); + pw.print(prefix); pw.print("mExpandedPanel="); pw.println(mNotificationShade); } - pw.print(" isKeyguardShowing="); pw.println(isKeyguardShowing()); + pw.print(prefix); pw.print("isKeyguardShowing="); pw.println(isKeyguardShowing()); if (mNavigationBar != null) { pw.print(prefix); pw.print("mNavigationBar="); pw.println(mNavigationBar); pw.print(prefix); pw.print("mNavBarOpacityMode="); pw.println(mNavBarOpacityMode); @@ -3415,9 +3426,9 @@ public class DisplayPolicy { } pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen); pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar); - pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars"); - pw.print(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars()); pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn); + pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars="); + pw.println(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars()); pw.print(prefix); pw.println("Looper state:"); mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + " "); diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index 251c01469c6a..231cc9745ce1 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -63,26 +63,8 @@ class EnsureActivitiesVisibleHelper { } /** - * Update visibility to activities. - * @see Task#ensureActivitiesVisible(ActivityRecord, int, boolean) - * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean) - * @param starting The top most activity in the task. - * The activity is either starting or resuming. - * Caller should ensure starting activity is visible. - * - */ - void processUpdate(@Nullable ActivityRecord starting) { - reset(starting, 0 /* configChanges */, false /* preserveWindows */, - false /* notifyClients */); - if (DEBUG_VISIBILITY) { - Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible processUpdate behind " + mTop); - } - - mTask.forAllActivities(this::updateActivityVisibility); - } - - /** - * Commit visibility with an option to also update the configuration of visible activities. + * Update and commit visibility with an option to also update the configuration of visible + * activities. * @see Task#ensureActivitiesVisible(ActivityRecord, int, boolean) * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean) * @param starting The top most activity in the task. @@ -95,12 +77,13 @@ class EnsureActivitiesVisibleHelper { * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc * be sent to the clients. */ - void processCommit(ActivityRecord starting, int configChanges, - boolean preserveWindows, boolean notifyClients) { + void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows, + boolean notifyClients) { reset(starting, configChanges, preserveWindows, notifyClients); if (DEBUG_VISIBILITY) { - Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible processCommit behind " + mTop); + Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTop + + " configChanges=0x" + Integer.toHexString(configChanges)); } if (mTop != null) { mTask.checkTranslucentActivityWaiting(mTop); @@ -114,25 +97,20 @@ class EnsureActivitiesVisibleHelper { && (starting == null || !starting.isDescendantOf(mTask)); mTask.forAllActivities(a -> { - commitActivityVisibility(a, starting, resumeTopActivity); + setActivityVisibilityState(a, starting, resumeTopActivity); }); } - private boolean isAboveTop(boolean isTop) { - if (mAboveTop && !isTop) { - return true; - } - mAboveTop = false; - return false; - } - - private void updateActivityVisibility(ActivityRecord r) { + private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting, + final boolean resumeTopActivity) { final boolean isTop = r == mTop; - if (isAboveTop(isTop)) { + if (mAboveTop && !isTop) { return; } + mAboveTop = false; - r.updateVisibility(mBehindFullscreenActivity); + r.updateVisibilityIgnoringKeyguard(mBehindFullscreenActivity); + final boolean reallyVisible = r.shouldBeVisibleUnchecked(); // Check whether activity should be visible without Keyguard influence if (r.visibleIgnoringKeyguard) { @@ -149,36 +127,14 @@ class EnsureActivitiesVisibleHelper { } } - if (!mBehindFullscreenActivity && mTask.isActivityTypeHome() && r.isRootOfTask()) { - if (DEBUG_VISIBILITY) { - Slog.v(TAG_VISIBILITY, "Home task: at " + mTask - + " stackShouldBeVisible=" + mContainerShouldBeVisible - + " behindFullscreenActivity=" + mBehindFullscreenActivity); - } - // No other task in the home stack should be visible behind the home activity. - // Home activities is usually a translucent activity with the wallpaper behind - // them. However, when they don't have the wallpaper behind them, we want to - // show activities in the next application stack behind them vs. another - // task in the home stack like recents. - mBehindFullscreenActivity = true; - } - } - - private void commitActivityVisibility(ActivityRecord r, ActivityRecord starting, - final boolean resumeTopActivity) { - final boolean isTop = r == mTop; - if (isAboveTop(isTop)) { - return; - } - - final boolean reallyVisible = r.shouldBeVisibleUnchecked(); - if (reallyVisible) { if (r.finishing) { return; } - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r - + " finishing=" + r.finishing + " state=" + r.getState()); + if (DEBUG_VISIBILITY) { + Slog.v(TAG_VISIBILITY, "Make visible? " + r + + " finishing=" + r.finishing + " state=" + r.getState()); + } // First: if this is not the current activity being started, make // sure it matches the current configuration. if (r != mStarting && mNotifyClients) { @@ -191,8 +147,9 @@ class EnsureActivitiesVisibleHelper { resumeTopActivity && isTop, r); } else if (r.mVisibleRequested) { // If this activity is already visible, then there is nothing to do here. - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "Skipping: already visible at " + r); + if (DEBUG_VISIBILITY) { + Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r); + } if (r.mClientVisibilityDeferred && mNotifyClients) { r.makeActiveIfNeeded(r.mClientVisibilityDeferred ? null : starting); @@ -209,13 +166,29 @@ class EnsureActivitiesVisibleHelper { // Aggregate current change flags. mConfigChanges |= r.configChangeFlags; } else { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r - + " finishing=" + r.finishing + " state=" + r.getState() - + " stackShouldBeVisible=" + mContainerShouldBeVisible - + " behindFullscreenActivity=" + mBehindFullscreenActivity - + " mLaunchTaskBehind=" + r.mLaunchTaskBehind); + if (DEBUG_VISIBILITY) { + Slog.v(TAG_VISIBILITY, "Make invisible? " + r + + " finishing=" + r.finishing + " state=" + r.getState() + + " stackShouldBeVisible=" + mContainerShouldBeVisible + + " behindFullscreenActivity=" + mBehindFullscreenActivity + + " mLaunchTaskBehind=" + r.mLaunchTaskBehind); + } r.makeInvisible(); } + + if (!mBehindFullscreenActivity && mTask.isActivityTypeHome() && r.isRootOfTask()) { + if (DEBUG_VISIBILITY) { + Slog.v(TAG_VISIBILITY, "Home task: at " + mTask + + " stackShouldBeVisible=" + mContainerShouldBeVisible + + " behindFullscreenActivity=" + mBehindFullscreenActivity); + } + // No other task in the home stack should be visible behind the home activity. + // Home activities is usually a translucent activity with the wallpaper behind + // them. However, when they don't have the wallpaper behind them, we want to + // show activities in the next application stack behind them vs. another + // task in the home stack like recents. + mBehindFullscreenActivity = true; + } } private void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges, @@ -230,12 +203,16 @@ class EnsureActivitiesVisibleHelper { // This activity needs to be visible, but isn't even running... // get it started and resume if no other stack in this stack is resumed. - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r); + if (DEBUG_VISIBILITY) { + Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r); + } if (r != starting) { r.startFreezingScreenLocked(configChanges); } if (!r.mVisibleRequested || r.mLaunchTaskBehind) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r); + if (DEBUG_VISIBILITY) { + Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r); + } r.setVisibility(true); } if (r != starting) { diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index f0f338534ed2..1d8cdf7e773c 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -138,8 +138,9 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider { && dcTarget.getParentWindow() == mImeTargetFromIme && dcTarget.mSubLayer > mImeTargetFromIme.getWindow().mSubLayer) || mImeTargetFromIme == mDisplayContent.getImeFallback() + || mImeTargetFromIme == mDisplayContent.mInputMethodInputTarget || controlTarget == mImeTargetFromIme - && (mImeTargetFromIme.getWindow() == null + && (mImeTargetFromIme.getWindow() == null || !mImeTargetFromIme.getWindow().isClosing()); } diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java index 8b1a0c93cfa3..02dad3978d42 100644 --- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java +++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java @@ -28,6 +28,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.drawable.ColorDrawable; import android.os.Binder; @@ -46,6 +47,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.WindowInsets; import android.view.WindowInsets.Type; import android.view.WindowManager; import android.view.animation.Animation; @@ -134,11 +136,8 @@ public class ImmersiveModeConfirmation { boolean userSetupComplete, boolean navBarEmpty) { mHandler.removeMessages(H.SHOW); if (isImmersiveMode) { - final boolean disabled = PolicyControl.disableImmersiveConfirmation(pkg); - if (DEBUG) Slog.d(TAG, String.format("immersiveModeChanged() disabled=%s sConfirmed=%s", - disabled, sConfirmed)); - if (!disabled - && (DEBUG_SHOW_EVERY_TIME || !sConfirmed) + if (DEBUG) Slog.d(TAG, "immersiveModeChanged() sConfirmed=" + sConfirmed); + if ((DEBUG_SHOW_EVERY_TIME || !sConfirmed) && userSetupComplete && !mVrModeEnabled && !navBarEmpty @@ -339,6 +338,13 @@ public class ImmersiveModeConfirmation { public boolean onTouchEvent(MotionEvent motion) { return true; } + + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + // we will be hiding the nav bar, so layout as if it's already hidden + return new WindowInsets.Builder(insets).setInsets( + Type.systemBars(), Insets.NONE).build(); + } } /** @@ -359,10 +365,6 @@ public class ImmersiveModeConfirmation { mClingWindow = new ClingWindowView(mContext, mConfirm); - // we will be hiding the nav bar, so layout as if it's already hidden - mClingWindow.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - // show the confirmation WindowManager.LayoutParams lp = getClingWindowLayoutParams(); getWindowManager().addView(mClingWindow, lp); diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index bad28ba333ba..80c7366a6678 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -125,6 +125,14 @@ class KeyguardController { } /** + * + * @return true if the activity is controlling keyguard state. + */ + boolean topActivityOccludesKeyguard(ActivityRecord r) { + return getDisplayState(r.getDisplayId()).mTopOccludesActivity == r; + } + + /** * @return {@code true} if the keyguard is going away, {@code false} otherwise. */ boolean isKeyguardGoingAway() { @@ -258,15 +266,14 @@ class KeyguardController { * @return True if we may show an activity while Keyguard is showing because we are in the * process of dismissing it anyways, false otherwise. */ - boolean canShowActivityWhileKeyguardShowing(ActivityRecord r, boolean dismissKeyguard) { - + boolean canShowActivityWhileKeyguardShowing(ActivityRecord r) { // Allow to show it when we are about to dismiss Keyguard. This isn't allowed if r is // already the dismissing activity, in which case we don't allow it to repeatedly dismiss // Keyguard. - return dismissKeyguard && canDismissKeyguard() && !mAodShowing + return r.containsDismissKeyguardWindow() && canDismissKeyguard() && !mAodShowing && (mDismissalRequested || (r.canShowWhenLocked() - && getDisplay(r.getDisplayId()).mDismissingKeyguardActivity != r)); + && getDisplayState(r.getDisplayId()).mDismissingKeyguardActivity != r)); } /** @@ -290,7 +297,7 @@ class KeyguardController { if (isKeyguardOrAodShowing(r.mDisplayContent.getDisplayId())) { // If keyguard is showing, nothing is visible, except if we are able to dismiss Keyguard // right away and AOD isn't visible. - return canShowActivityWhileKeyguardShowing(r, r.containsDismissKeyguardWindow()); + return canShowActivityWhileKeyguardShowing(r); } else if (isKeyguardLocked()) { return canShowWhileOccluded(r.containsDismissKeyguardWindow(), r.canShowWhenLocked()); } else { @@ -299,16 +306,17 @@ class KeyguardController { } /** - * Makes sure to update lockscreen occluded/dismiss state if needed after completing all - * visibility updates ({@link ActivityStackSupervisor#endActivityVisibilityUpdate}). + * Makes sure to update lockscreen occluded/dismiss/turnScreenOn state if needed before + * completing set all visibility + * ({@link ActivityStackSupervisor#beginActivityVisibilityUpdate}). */ - void visibilitiesUpdated() { + void updateVisibility() { boolean requestDismissKeyguard = false; for (int displayNdx = mRootWindowContainer.getChildCount() - 1; displayNdx >= 0; displayNdx--) { final DisplayContent display = mRootWindowContainer.getChildAt(displayNdx); - final KeyguardDisplayState state = getDisplay(display.mDisplayId); - state.visibilitiesUpdated(this, display); + final KeyguardDisplayState state = getDisplayState(display.mDisplayId); + state.updateVisibility(this, display); requestDismissKeyguard |= state.mRequestDismissKeyguard; } @@ -340,7 +348,6 @@ class KeyguardController { false /* alwaysKeepCurrent */, 0 /* flags */, true /* forceOverride */); updateKeyguardSleepToken(DEFAULT_DISPLAY); - mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); mWindowManager.executeAppTransition(); } finally { mService.continueWindowLayout(); @@ -370,13 +377,12 @@ class KeyguardController { && dc.mAppTransition.getAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE) { dc.prepareAppTransition(mBeforeUnoccludeTransit, false /* alwaysKeepCurrent */, 0 /* flags */, true /* forceOverride */); - mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); mWindowManager.executeAppTransition(); } } - private boolean isDisplayOccluded(int displayId) { - return getDisplay(displayId).mOccluded; + boolean isDisplayOccluded(int displayId) { + return getDisplayState(displayId).mOccluded; } /** @@ -433,7 +439,7 @@ class KeyguardController { } private void updateKeyguardSleepToken(int displayId) { - final KeyguardDisplayState state = getDisplay(displayId); + final KeyguardDisplayState state = getDisplayState(displayId); if (isKeyguardUnoccludedOrAodShowing(displayId)) { state.mSleepTokenAcquirer.acquire(displayId); } else if (!isKeyguardUnoccludedOrAodShowing(displayId)) { @@ -441,7 +447,7 @@ class KeyguardController { } } - private KeyguardDisplayState getDisplay(int displayId) { + private KeyguardDisplayState getDisplayState(int displayId) { KeyguardDisplayState state = mDisplayStates.get(displayId); if (state == null) { state = new KeyguardDisplayState(mService, displayId, mSleepTokenAcquirer); @@ -462,8 +468,11 @@ class KeyguardController { private static class KeyguardDisplayState { private final int mDisplayId; private boolean mOccluded; + + private ActivityRecord mTopOccludesActivity; private ActivityRecord mDismissingKeyguardActivity; private ActivityRecord mTopTurnScreenOnActivity; + private boolean mRequestDismissKeyguard; private final ActivityTaskManagerService mService; private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer; @@ -476,58 +485,77 @@ class KeyguardController { } void onRemoved() { + mTopOccludesActivity = null; mDismissingKeyguardActivity = null; mTopTurnScreenOnActivity = null; mSleepTokenAcquirer.release(mDisplayId); } - void visibilitiesUpdated(KeyguardController controller, DisplayContent display) { + /** + * Updates {@link #mOccluded}, {@link #mTopTurnScreenOnActivity} and + * {@link #mDismissingKeyguardActivity} if the top task could be visible. + */ + void updateVisibility(KeyguardController controller, DisplayContent display) { final boolean lastOccluded = mOccluded; - final ActivityRecord lastDismissActivity = mDismissingKeyguardActivity; + + final ActivityRecord lastDismissKeyguardActivity = mDismissingKeyguardActivity; final ActivityRecord lastTurnScreenOnActivity = mTopTurnScreenOnActivity; + mRequestDismissKeyguard = false; mOccluded = false; + + mTopOccludesActivity = null; mDismissingKeyguardActivity = null; mTopTurnScreenOnActivity = null; - // only top + focusable + visible task can control occluding. - final Task stack = getStackForControllingOccluding(display); - if (stack != null) { - final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity(); - final ActivityRecord topTurnScreenOn = stack.getTopTurnScreenOnActivity(); - mOccluded = stack.topActivityOccludesKeyguard() || (topDismissing != null - && stack.topRunningActivity() == topDismissing - && controller.canShowWhileOccluded( - true /* dismissKeyguard */, - false /* showWhenLocked */)); - if (topDismissing != null) { - mDismissingKeyguardActivity = topDismissing; - } - if (topTurnScreenOn != null) { - mTopTurnScreenOnActivity = topTurnScreenOn; - } - // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display. - if (mDisplayId != DEFAULT_DISPLAY && stack.mDisplayContent != null) { - mOccluded |= stack.mDisplayContent.canShowWithInsecureKeyguard() - && controller.canDismissKeyguard(); + boolean occludedByActivity = false; + final Task task = getRootTaskForControllingOccluding(display); + if (task != null) { + final ActivityRecord r = task.getTopNonFinishingActivity(); + if (r != null) { + final boolean showWhenLocked = r.canShowWhenLocked(); + if (r.containsDismissKeyguardWindow()) { + mDismissingKeyguardActivity = r; + } + if (r.getTurnScreenOnFlag() + && r.currentLaunchCanTurnScreenOn()) { + mTopTurnScreenOnActivity = r; + } + + if (showWhenLocked) { + mTopOccludesActivity = r; + } + + // Only the top activity may control occluded, as we can't occlude the Keyguard + // if the top app doesn't want to occlude it. + occludedByActivity = showWhenLocked || (mDismissingKeyguardActivity != null + && task.topRunningActivity() == mDismissingKeyguardActivity + && controller.canShowWhileOccluded( + true /* dismissKeyguard */, false /* showWhenLocked */)); + // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display. + if (mDisplayId != DEFAULT_DISPLAY && task.mDisplayContent != null) { + occludedByActivity |= + task.mDisplayContent.canShowWithInsecureKeyguard() + && controller.canDismissKeyguard(); + } } } // TODO(b/123372519): isShowingDream can only works on default display. - if (mDisplayId == DEFAULT_DISPLAY) { - mOccluded |= mService.mRootWindowContainer.getDefaultDisplay() - .getDisplayPolicy().isShowingDreamLw(); - } - - mRequestDismissKeyguard = lastDismissActivity != mDismissingKeyguardActivity + mOccluded = occludedByActivity || (mDisplayId == DEFAULT_DISPLAY + && mService.mRootWindowContainer.getDefaultDisplay() + .getDisplayPolicy().isShowingDreamLw()); + mRequestDismissKeyguard = lastDismissKeyguardActivity != mDismissingKeyguardActivity && !mOccluded && mDismissingKeyguardActivity != null && controller.mWindowManager.isKeyguardSecure( controller.mService.getCurrentUserId()); - if (mTopTurnScreenOnActivity != null - && mTopTurnScreenOnActivity != lastTurnScreenOnActivity - && !mService.mWindowManager.mPowerManager.isInteractive()) { + if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity + && mTopTurnScreenOnActivity != null + && !mService.mWindowManager.mPowerManager.isInteractive() + && (mRequestDismissKeyguard || occludedByActivity)) { controller.mStackSupervisor.wakeUp("handleTurnScreenOn"); + mTopOccludesActivity.setCurrentLaunchCanTurnScreenOn(false); } if (lastOccluded != mOccluded) { @@ -542,13 +570,13 @@ class KeyguardController { * occlusion state. */ @Nullable - private Task getStackForControllingOccluding(DisplayContent display) { + private Task getRootTaskForControllingOccluding(DisplayContent display) { return display.getItemFromTaskDisplayAreas(taskDisplayArea -> { for (int sNdx = taskDisplayArea.getStackCount() - 1; sNdx >= 0; --sNdx) { - final Task stack = taskDisplayArea.getStackAt(sNdx); - if (stack != null && stack.isFocusableAndVisible() - && !stack.inPinnedWindowingMode()) { - return stack; + final Task task = taskDisplayArea.getStackAt(sNdx); + if (task != null && task.isFocusableAndVisible() + && !task.inPinnedWindowingMode()) { + return task; } } return null; diff --git a/services/core/java/com/android/server/wm/PolicyControl.java b/services/core/java/com/android/server/wm/PolicyControl.java deleted file mode 100644 index 61b6e0b25961..000000000000 --- a/services/core/java/com/android/server/wm/PolicyControl.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import android.app.ActivityManager; -import android.content.Context; -import android.os.UserHandle; -import android.provider.Settings; -import android.util.ArraySet; -import android.util.Slog; -import android.view.View; -import android.view.WindowManager; -import android.view.WindowManager.LayoutParams; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.PrintWriter; -import java.io.StringWriter; - -/** - * Runtime adjustments applied to the global window policy. - * - * This includes forcing immersive mode behavior for one or both system bars (based on a package - * list) and permanently disabling immersive mode confirmations for specific packages. - * - * Control by setting {@link Settings.Global#POLICY_CONTROL} to one or more name-value pairs. - * e.g. - * to force immersive mode everywhere: - * "immersive.full=*" - * to force transient status for all apps except a specific package: - * "immersive.status=apps,-com.package" - * to disable the immersive mode confirmations for specific packages: - * "immersive.preconfirms=com.package.one,com.package.two" - * - * Separate multiple name-value pairs with ':' - * e.g. "immersive.status=apps:immersive.preconfirms=*" - */ -class PolicyControl { - private static final String TAG = "PolicyControl"; - private static final boolean DEBUG = false; - - @VisibleForTesting - static final String NAME_IMMERSIVE_FULL = "immersive.full"; - private static final String NAME_IMMERSIVE_STATUS = "immersive.status"; - private static final String NAME_IMMERSIVE_NAVIGATION = "immersive.navigation"; - private static final String NAME_IMMERSIVE_PRECONFIRMATIONS = "immersive.preconfirms"; - - private static String sSettingValue; - private static Filter sImmersivePreconfirmationsFilter; - private static Filter sImmersiveStatusFilter; - private static Filter sImmersiveNavigationFilter; - - static int getSystemUiVisibility(WindowState win, LayoutParams attrs) { - attrs = attrs != null ? attrs : win.getAttrs(); - int vis = win != null ? win.getSystemUiVisibility() - : (attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility); - if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) { - vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_FULLSCREEN; - if (attrs.isFullscreen()) { - vis |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - } - vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.STATUS_BAR_TRANSLUCENT); - } - if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) { - vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; - if (attrs.isFullscreen()) { - vis |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - } - vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.NAVIGATION_BAR_TRANSLUCENT); - } - return vis; - } - - static int getWindowFlags(WindowState win, LayoutParams attrs) { - attrs = attrs != null ? attrs : win.getAttrs(); - int flags = attrs.flags; - if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) { - flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; - flags &= ~(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS - | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - } - if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) { - flags &= ~WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; - } - return flags; - } - - static int adjustClearableFlags(WindowState win, int clearableFlags) { - final LayoutParams attrs = win != null ? win.getAttrs() : null; - if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) { - clearableFlags &= ~View.SYSTEM_UI_FLAG_FULLSCREEN; - } - return clearableFlags; - } - - static boolean disableImmersiveConfirmation(String pkg) { - return (sImmersivePreconfirmationsFilter != null - && sImmersivePreconfirmationsFilter.matches(pkg)) - || ActivityManager.isRunningInTestHarness(); - } - - static boolean reloadFromSetting(Context context) { - if (DEBUG) Slog.d(TAG, "reloadFromSetting()"); - String value = null; - try { - value = Settings.Global.getStringForUser(context.getContentResolver(), - Settings.Global.POLICY_CONTROL, - UserHandle.USER_CURRENT); - if (sSettingValue == value || sSettingValue != null && sSettingValue.equals(value)) { - return false; - } - setFilters(value); - sSettingValue = value; - } catch (Throwable t) { - Slog.w(TAG, "Error loading policy control, value=" + value, t); - return false; - } - return true; - } - - static void dump(String prefix, PrintWriter pw) { - dump("sImmersiveStatusFilter", sImmersiveStatusFilter, prefix, pw); - dump("sImmersiveNavigationFilter", sImmersiveNavigationFilter, prefix, pw); - dump("sImmersivePreconfirmationsFilter", sImmersivePreconfirmationsFilter, prefix, pw); - } - - private static void dump(String name, Filter filter, String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("PolicyControl."); pw.print(name); pw.print('='); - if (filter == null) { - pw.println("null"); - } else { - filter.dump(pw); pw.println(); - } - } - - @VisibleForTesting - static void setFilters(String value) { - if (DEBUG) Slog.d(TAG, "setFilters: " + value); - sImmersiveStatusFilter = null; - sImmersiveNavigationFilter = null; - sImmersivePreconfirmationsFilter = null; - if (value != null) { - String[] nvps = value.split(":"); - for (String nvp : nvps) { - int i = nvp.indexOf('='); - if (i == -1) continue; - String n = nvp.substring(0, i); - String v = nvp.substring(i + 1); - if (n.equals(NAME_IMMERSIVE_FULL)) { - Filter f = Filter.parse(v); - sImmersiveStatusFilter = sImmersiveNavigationFilter = f; - if (sImmersivePreconfirmationsFilter == null) { - sImmersivePreconfirmationsFilter = f; - } - } else if (n.equals(NAME_IMMERSIVE_STATUS)) { - Filter f = Filter.parse(v); - sImmersiveStatusFilter = f; - } else if (n.equals(NAME_IMMERSIVE_NAVIGATION)) { - Filter f = Filter.parse(v); - sImmersiveNavigationFilter = f; - if (sImmersivePreconfirmationsFilter == null) { - sImmersivePreconfirmationsFilter = f; - } - } else if (n.equals(NAME_IMMERSIVE_PRECONFIRMATIONS)) { - Filter f = Filter.parse(v); - sImmersivePreconfirmationsFilter = f; - } - } - } - if (DEBUG) { - Slog.d(TAG, "immersiveStatusFilter: " + sImmersiveStatusFilter); - Slog.d(TAG, "immersiveNavigationFilter: " + sImmersiveNavigationFilter); - Slog.d(TAG, "immersivePreconfirmationsFilter: " + sImmersivePreconfirmationsFilter); - } - } - - private static class Filter { - private static final String ALL = "*"; - private static final String APPS = "apps"; - - private final ArraySet<String> mAllowlist; - private final ArraySet<String> mDenylist; - - private Filter(ArraySet<String> allowlist, ArraySet<String> denylist) { - mAllowlist = allowlist; - mDenylist = denylist; - } - - boolean matches(LayoutParams attrs) { - if (attrs == null) return false; - boolean isApp = attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW - && attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; - if (isApp && mDenylist.contains(APPS)) return false; - if (onDenylist(attrs.packageName)) return false; - if (isApp && mAllowlist.contains(APPS)) return true; - return onAllowlist(attrs.packageName); - } - - boolean matches(String packageName) { - return !onDenylist(packageName) && onAllowlist(packageName); - } - - private boolean onDenylist(String packageName) { - return mDenylist.contains(packageName) || mDenylist.contains(ALL); - } - - private boolean onAllowlist(String packageName) { - return mAllowlist.contains(ALL) || mAllowlist.contains(packageName); - } - - void dump(PrintWriter pw) { - pw.print("Filter["); - dump("allowlist", mAllowlist, pw); pw.print(','); - dump("denylist", mDenylist, pw); pw.print(']'); - } - - private void dump(String name, ArraySet<String> set, PrintWriter pw) { - pw.print(name); pw.print("=("); - final int n = set.size(); - for (int i = 0; i < n; i++) { - if (i > 0) pw.print(','); - pw.print(set.valueAt(i)); - } - pw.print(')'); - } - - @Override - public String toString() { - StringWriter sw = new StringWriter(); - dump(new PrintWriter(sw, true)); - return sw.toString(); - } - - // value = comma-delimited list of tokens, where token = (package name|apps|*) - // e.g. "com.package1", or "apps, com.android.keyguard" or "*" - static Filter parse(String value) { - if (value == null) return null; - ArraySet<String> allowlist = new ArraySet<String>(); - ArraySet<String> denylist = new ArraySet<String>(); - for (String token : value.split(",")) { - token = token.trim(); - if (token.startsWith("-") && token.length() > 1) { - token = token.substring(1); - denylist.add(token); - } else { - allowlist.add(token); - } - } - return new Filter(allowlist, denylist); - } - } -} diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index b50cb4c34398..42a20ff7ed7b 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -929,7 +929,7 @@ public class RecentsAnimationController implements DeathRecipient { ? MODE_OPENING : MODE_CLOSING; mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash, - !topApp.fillsParent(), mainWindow.mWinAnimator.mLastClipRect, + !topApp.fillsParent(), new Rect(), insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top), mLocalBounds, mBounds, mTask.getWindowConfiguration(), mIsRecentTaskInvisible, null, null); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 71ecf725ab80..2df56a3ec843 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -276,6 +276,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Whether tasks have moved and we need to rank the tasks before next OOM scoring private boolean mTaskLayersChanged = true; private int mTmpTaskLayerRank; + private final ArraySet<WindowProcessController> mTmpTaskLayerChangedProcs = new ArraySet<>(); + private final LockedScheduler mRankTaskLayersScheduler; private boolean mTmpBoolean; private RemoteException mTmpRemoteException; @@ -450,6 +452,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mStackSupervisor = mService.mStackSupervisor; mStackSupervisor.mRootWindowContainer = this; mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl("Display-off"); + mRankTaskLayersScheduler = new LockedScheduler(mService) { + @Override + public void execute() { + rankTaskLayersIfNeeded(); + } + }; } boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { @@ -1995,22 +2003,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> notifyClients); } } finally { - mStackSupervisor.endActivityVisibilityUpdate(starting, configChanges, preserveWindows, - notifyClients); + mStackSupervisor.endActivityVisibilityUpdate(); } } - void commitActivitiesVisible(ActivityRecord starting, int configChanges, - boolean preserveWindows, boolean notifyClients) { - forAllTaskDisplayAreas(taskDisplayArea -> { - for (int stackNdx = taskDisplayArea.getStackCount() - 1; stackNdx >= 0; --stackNdx) { - final Task task = taskDisplayArea.getStackAt(stackNdx); - task.commitActivitiesVisible(starting, configChanges, preserveWindows, - notifyClients); - } - }); - } - boolean switchUser(int userId, UserState uss) { final Task topFocusedStack = getTopDisplayFocusedStack(); final int focusStackId = topFocusedStack != null @@ -2698,27 +2694,39 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void invalidateTaskLayers() { mTaskLayersChanged = true; + mRankTaskLayersScheduler.scheduleIfNeeded(); } + /** Generate oom-score-adjustment rank for all tasks in the system based on z-order. */ void rankTaskLayersIfNeeded() { if (!mTaskLayersChanged) { return; } mTaskLayersChanged = false; mTmpTaskLayerRank = 0; - final PooledConsumer c = PooledLambda.obtainConsumer( - RootWindowContainer::rankTaskLayerForActivity, this, - PooledLambda.__(ActivityRecord.class)); - forAllActivities(c); - c.recycle(); - } + // Only rank for leaf tasks because the score of activity is based on immediate parent. + forAllLeafTasks(task -> { + final int oldRank = task.mLayerRank; + final ActivityRecord r = task.topRunningActivityLocked(); + if (r != null && r.mVisibleRequested) { + task.mLayerRank = ++mTmpTaskLayerRank; + } else { + task.mLayerRank = Task.LAYER_RANK_INVISIBLE; + } + if (task.mLayerRank != oldRank) { + task.forAllActivities(activity -> { + if (activity.hasProcess()) { + mTmpTaskLayerChangedProcs.add(activity.app); + } + }); + } + }, true /* traverseTopToBottom */); - private void rankTaskLayerForActivity(ActivityRecord r) { - if (r.canBeTopRunning() && r.mVisibleRequested) { - r.getTask().mLayerRank = ++mTmpTaskLayerRank; - } else { - r.getTask().mLayerRank = -1; + for (int i = mTmpTaskLayerChangedProcs.size() - 1; i >= 0; i--) { + mTmpTaskLayerChangedProcs.valueAt(i).invalidateOomScoreReferenceState( + true /* computeNow */); } + mTmpTaskLayerChangedProcs.clear(); } void clearOtherAppTimeTrackers(AppTimeTracker except) { @@ -3680,4 +3688,34 @@ class RootWindowContainer extends WindowContainer<DisplayContent> + ", acquire at " + TimeUtils.formatUptime(mAcquireTime) + "}"; } } + + /** + * Helper class to schedule the runnable if it hasn't scheduled on display thread inside window + * manager lock. + */ + abstract static class LockedScheduler implements Runnable { + private final ActivityTaskManagerService mService; + private boolean mScheduled; + + LockedScheduler(ActivityTaskManagerService service) { + mService = service; + } + + @Override + public void run() { + synchronized (mService.mGlobalLock) { + mScheduled = false; + execute(); + } + } + + abstract void execute(); + + void scheduleIfNeeded() { + if (!mScheduled) { + mService.mH.post(this); + mScheduled = true; + } + } + } } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 5f2113afa5b2..9ff99f5093d6 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -157,33 +157,33 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override - public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, + public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { - return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, + return mService.addWindow(this, window, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, outInsetsState, outActiveControls, UserHandle.getUserId(mUid)); } @Override - public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs, + public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, int userId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { - return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, + return mService.addWindow(this, window, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, outInsetsState, outActiveControls, userId); } @Override - public int addToDisplayWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs, + public int addToDisplayWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, InsetsState outInsetsState) { - return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, + return mService.addWindow(this, window, attrs, viewVisibility, displayId, new Rect() /* outFrame */, outContentInsets, outStableInsets, new DisplayCutout.ParcelableWrapper() /* cutout */, null /* outInputChannel */, outInsetsState, mDummyControls, UserHandle.getUserId(mUid)); @@ -200,7 +200,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override - public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs, + public int relayout(IWindow window, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, @@ -209,7 +209,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag); - int res = mService.relayoutWindow(this, window, seq, attrs, + int res = mService.relayoutWindow(this, window, attrs, requestedWidth, requestedHeight, viewFlags, flags, frameNumber, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState, outActiveControls, outSurfaceSize, outBLASTSurfaceControl); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index ce602de702d6..22776e0d9ee0 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -464,9 +464,10 @@ class Task extends WindowContainer<WindowContainer> { int mMinWidth; int mMinHeight; + static final int LAYER_RANK_INVISIBLE = -1; // Ranking (from top) of this task among all visible tasks. (-1 means it's not visible) // This number will be assigned when we evaluate OOM scores for all visible tasks. - int mLayerRank = -1; + int mLayerRank = LAYER_RANK_INVISIBLE; /** Helper object used for updating override configuration. */ private Configuration mTmpConfig = new Configuration(); @@ -596,10 +597,6 @@ class Task extends WindowContainer<WindowContainer> { private final AnimatingActivityRegistry mAnimatingActivityRegistry = new AnimatingActivityRegistry(); - private boolean mTopActivityOccludesKeyguard; - private ActivityRecord mTopDismissingKeyguardActivity; - private ActivityRecord mTopTurnScreenOnActivity; - private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 1; private final Handler mHandler; @@ -1539,7 +1536,6 @@ class Task extends WindowContainer<WindowContainer> { if (isPersistable) { mLastTimeMoved = System.currentTimeMillis(); } - mRootWindowContainer.invalidateTaskLayers(); } // Close up recents linked list. @@ -2376,6 +2372,7 @@ class Task extends WindowContainer<WindowContainer> { private void initializeChangeTransition(Rect startBounds) { mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE, false /* alwaysKeepCurrent */, 0, false /* forceOverride */); + mAtmService.getTransitionController().collect(this); mDisplayContent.mChangingContainers.add(this); mSurfaceFreezer.freeze(getPendingTransaction(), startBounds); @@ -4766,6 +4763,16 @@ class Task extends WindowContainer<WindowContainer> { } @Override + boolean showSurfaceOnCreation() { + // Organized tasks handle their own surface visibility + final boolean willBeOrganized = + mAtmService.mTaskOrganizerController.isSupportedWindowingMode(getWindowingMode()) + && isRootTask(); + return !mAtmService.getTransitionController().isShellTransitionsEnabled() + || !willBeOrganized; + } + + @Override protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) { /** * Avoid reparenting SurfaceControl of the organized tasks that are always on top, since @@ -4785,7 +4792,9 @@ class Task extends WindowContainer<WindowContainer> { // hide it to allow the task organizer to show it when it is properly reparented. We // skip this for tasks created by the organizer because they can synchronously update // the leash before new children are added to the task. - if (!mCreatedByOrganizer && mTaskOrganizer != null && !prevHasBeenVisible) { + if (!mAtmService.getTransitionController().isShellTransitionsEnabled() + && !mCreatedByOrganizer + && mTaskOrganizer != null && !prevHasBeenVisible) { getSyncTransaction().hide(getSurfaceControl()); commitPendingTransaction(); } @@ -5690,12 +5699,10 @@ class Task extends WindowContainer<WindowContainer> { // TODO: Should be re-worked based on the fact that each task as a stack in most cases. void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows, boolean notifyClients) { - mTopActivityOccludesKeyguard = false; - mTopDismissingKeyguardActivity = null; - mTopTurnScreenOnActivity = null; mStackSupervisor.beginActivityVisibilityUpdate(); try { - mEnsureActivitiesVisibleHelper.processUpdate(starting); + mEnsureActivitiesVisibleHelper.process(starting, configChanges, preserveWindows, + notifyClients); if (mTranslucentActivityWaiting != null && mUndrawnActivitiesBelowTopTranslucent.isEmpty()) { @@ -5704,24 +5711,10 @@ class Task extends WindowContainer<WindowContainer> { notifyActivityDrawnLocked(null); } } finally { - mStackSupervisor.endActivityVisibilityUpdate(starting, configChanges, preserveWindows, - notifyClients); + mStackSupervisor.endActivityVisibilityUpdate(); } } - void commitActivitiesVisible(ActivityRecord starting, int configChanges, - boolean preserveWindows, boolean notifyClients) { - mEnsureActivitiesVisibleHelper.processCommit(starting, configChanges, preserveWindows, - notifyClients); - } - - /** - * @return true if the top visible activity wants to occlude the Keyguard, false otherwise - */ - boolean topActivityOccludesKeyguard() { - return mTopActivityOccludesKeyguard; - } - /** * Returns true if this stack should be resized to match the bounds specified by * {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack. @@ -5740,44 +5733,6 @@ class Task extends WindowContainer<WindowContainer> { && this == getDisplayArea().getTopStackInWindowingMode(getWindowingMode()); } - /** - * @return the top most visible activity that wants to dismiss Keyguard - */ - ActivityRecord getTopDismissingKeyguardActivity() { - return mTopDismissingKeyguardActivity; - } - - /** - * @return the top most visible activity that wants to turn screen on - */ - ActivityRecord getTopTurnScreenOnActivity() { - return mTopTurnScreenOnActivity; - } - - /** - * Updates {@link #mTopActivityOccludesKeyguard}, {@link #mTopTurnScreenOnActivity} and - * {@link #mTopDismissingKeyguardActivity} if this task could be visible. - * - */ - void updateKeyguardVisibility(ActivityRecord r, boolean isTop) { - final boolean showWhenLocked = r.canShowWhenLocked(); - final boolean dismissKeyguard = r.containsDismissKeyguardWindow(); - final boolean turnScreenOn = r.canTurnScreenOn(); - if (dismissKeyguard && mTopDismissingKeyguardActivity == null) { - mTopDismissingKeyguardActivity = r; - } - - if (turnScreenOn && mTopTurnScreenOnActivity == null) { - mTopTurnScreenOnActivity = r; - } - - // Only the top activity may control occluded, as we can't occlude the Keyguard if the - // top app doesn't want to occlude it. - if (isTop) { - mTopActivityOccludesKeyguard |= showWhenLocked; - } - } - void checkTranslucentActivityWaiting(ActivityRecord top) { if (mTranslucentActivityWaiting != top) { mUndrawnActivitiesBelowTopTranslucent.clear(); @@ -6420,7 +6375,15 @@ class Task extends WindowContainer<WindowContainer> { transit = TRANSIT_TASK_OPEN; } } - dc.prepareAppTransition(transit, keepCurTransition); + if (mAtmService.getTransitionController().isShellTransitionsEnabled() + // TODO(shell-transitions): eventually all transitions. + && transit == TRANSIT_TASK_OPEN) { + Transition transition = + mAtmService.getTransitionController().requestTransition(transit); + transition.collect(task); + } else { + dc.prepareAppTransition(transit, keepCurTransition); + } mStackSupervisor.mNoAnimActivities.remove(r); } boolean doShow = true; @@ -7452,6 +7415,10 @@ class Task extends WindowContainer<WindowContainer> { if (!mChildren.contains(child)) { return; } + if (child.asTask() != null) { + // Non-root task position changed. + mRootWindowContainer.invalidateTaskLayers(); + } final boolean isTop = getTopChild() == child; if (isTop) { diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 76bd6ce380e9..2c8770f110fa 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -441,6 +441,12 @@ final class TaskDisplayArea extends DisplayArea<Task> { } @Override + void onChildPositionChanged(WindowContainer child) { + super.onChildPositionChanged(child); + mRootWindowContainer.invalidateTaskLayers(); + } + + @Override boolean forAllTaskDisplayAreas(Function<TaskDisplayArea, Boolean> callback, boolean traverseTopToBottom) { return callback.apply(this); @@ -1535,20 +1541,6 @@ final class TaskDisplayArea extends DisplayArea<Task> { return stack == getTopStack(); } - boolean isTopNotFinishNotPinnedStack(Task stack) { - for (int i = getStackCount() - 1; i >= 0; --i) { - final Task current = getStackAt(i); - final ActivityRecord topAct = current.getTopNonFinishingActivity(); - if (topAct == null) { - continue; - } - if (!current.inPinnedWindowingMode()) { - return current == stack; - } - } - return false; - } - ActivityRecord topRunningActivity() { return topRunningActivity(false /* considerKeyguardState */); } @@ -1827,8 +1819,7 @@ final class TaskDisplayArea extends DisplayArea<Task> { notifyClients); } } finally { - mAtmService.mStackSupervisor.endActivityVisibilityUpdate(starting, configChanges, - preserveWindows, notifyClients); + mAtmService.mStackSupervisor.endActivityVisibilityUpdate(); } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index e07c5677214b..8201d108c883 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -354,7 +354,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { : null; } - private boolean isSupportedWindowingMode(int winMode) { + boolean isSupportedWindowingMode(int winMode) { return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode); } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index cccda3a4f4b8..6904740343a6 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -241,7 +241,7 @@ class TaskSnapshotSurface implements StartingSurface { mergeInsetsSources(insetsState, topFullscreenOpaqueWindow.getRequestedInsetsState()); } try { - final int res = session.addToDisplay(window, window.mSeq, layoutParams, + final int res = session.addToDisplay(window, layoutParams, View.GONE, activity.getDisplayContent().getDisplayId(), tmpFrames.frame, tmpFrames.contentInsets, tmpFrames.stableInsets, tmpFrames.displayCutout, null /* outInputChannel */, mTmpInsetsState, mTempControls); @@ -258,7 +258,7 @@ class TaskSnapshotSurface implements StartingSurface { insetsState); window.setOuter(snapshotSurface); try { - session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1, + session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, -1, tmpFrames, tmpMergedConfiguration, surfaceControl, mTmpInsetsState, mTempControls, sTmpSurfaceSize, sTmpSurfaceControl); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java new file mode 100644 index 000000000000..fc67cd22ee69 --- /dev/null +++ b/services/core/java/com/android/server/wm/Transition.java @@ -0,0 +1,487 @@ +/* + * 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.wm; + + +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER; + +import android.annotation.NonNull; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; +import android.view.Display; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.window.TransitionInfo; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.ProtoLogGroup; +import com.android.internal.protolog.common.ProtoLog; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; + +/** + * Represents a logical transition. + * @see TransitionController + */ +class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener { + private static final String TAG = "Transition"; + + /** The transition has been created and is collecting, but hasn't formally started. */ + private static final int STATE_COLLECTING = 0; + + /** + * The transition has formally started. It is still collecting but will stop once all + * participants are ready to animate (finished drawing). + */ + private static final int STATE_STARTED = 1; + + /** + * This transition is currently playing its animation and can no longer collect or be changed. + */ + private static final int STATE_PLAYING = 2; + + final @WindowManager.TransitionType int mType; + private int mSyncId; + private @WindowManager.TransitionFlags int mFlags; + private final TransitionController mController; + final ArrayMap<WindowContainer, ChangeInfo> mParticipants = new ArrayMap<>(); + private int mState = STATE_COLLECTING; + private boolean mReadyCalled = false; + + Transition(@WindowManager.TransitionType int type, + @WindowManager.TransitionFlags int flags, TransitionController controller) { + mType = type; + mFlags = flags; + mController = controller; + mSyncId = mController.mSyncEngine.startSyncSet(this); + } + + /** + * Formally starts the transition. Participants can be collected before this is started, + * but this won't consider itself ready until started -- even if all the participants have + * drawn. + */ + void start() { + if (mState >= STATE_STARTED) { + Slog.w(TAG, "Transition already started: " + mSyncId); + } + mState = STATE_STARTED; + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d", + mSyncId); + if (mReadyCalled) { + setReady(); + } + } + + /** Adds wc to set of WindowContainers participating in this transition. */ + void collect(@NonNull WindowContainer wc) { + if (mSyncId < 0) return; + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s", + mSyncId, wc); + // Add to sync set before checking contains because it may not have added it at other + // times (eg. if wc was previously invisible). + mController.mSyncEngine.addToSyncSet(mSyncId, wc); + if (mParticipants.containsKey(wc)) return; + mParticipants.put(wc, new ChangeInfo()); + } + + /** + * Call this when all known changes related to this transition have been applied. Until + * all participants have finished drawing, the transition can still collect participants. + * + * If this is called before the transition is started, it will be deferred until start. + */ + void setReady() { + if (mSyncId < 0) return; + if (mState < STATE_STARTED) { + mReadyCalled = true; + return; + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Finish collecting in transition %d", mSyncId); + mController.mSyncEngine.setReady(mSyncId); + mController.mAtm.mWindowManager.mWindowPlacerLocked.requestTraversal(); + } + + /** The transition has finished animating and is ready to finalize WM state */ + void finishTransition() { + if (mState < STATE_PLAYING) { + throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); + } + for (int i = 0; i < mParticipants.size(); ++i) { + final ActivityRecord ar = mParticipants.keyAt(i).asActivityRecord(); + if (ar == null || ar.mVisibleRequested) { + continue; + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " Commit activity becoming invisible: %s", ar); + ar.commitVisibility(false /* visible */, false /* performLayout */); + } + } + + @Override + public void onTransactionReady(int syncId, Set<WindowContainer> windowContainersReady) { + if (syncId != mSyncId) { + Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId); + return; + } + mState = STATE_PLAYING; + mController.moveToPlaying(this); + final TransitionInfo info = calculateTransitionInfo(mType, mParticipants); + + SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction(); + int displayId = Display.DEFAULT_DISPLAY; + for (WindowContainer container : windowContainersReady) { + container.mergeBlastSyncTransaction(mergedTransaction); + displayId = container.mDisplayContent.getDisplayId(); + } + + handleNonAppWindowsInTransition(displayId, mType, mFlags); + + if (mController.getTransitionPlayer() != null) { + try { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Calling onTransitionReady: %s", info); + mController.getTransitionPlayer().onTransitionReady(this, info, mergedTransaction); + } catch (RemoteException e) { + // If there's an exception when trying to send the mergedTransaction to the + // client, we should immediately apply it here so the transactions aren't lost. + mergedTransaction.apply(); + } + } else { + mergedTransaction.apply(); + } + mSyncId = -1; + } + + private void handleNonAppWindowsInTransition(int displayId, int transit, int flags) { + final DisplayContent dc = + mController.mAtm.mRootWindowContainer.getDisplayContent(displayId); + if (dc == null) { + return; + } + if (transit == TRANSIT_KEYGUARD_GOING_AWAY) { + if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0 + && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0 + && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) { + Animation anim = mController.mAtm.mWindowManager.mPolicy + .createKeyguardWallpaperExit( + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0); + if (anim != null) { + anim.scaleCurrentDuration( + mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked()); + dc.mWallpaperController.startWallpaperAnimation(anim); + } + } + } + if (transit == TRANSIT_KEYGUARD_GOING_AWAY + || transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) { + dc.startKeyguardExitOnNonAppWindows( + transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER, + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0, + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0); + mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(transit, 0); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(64); + sb.append("TransitionRecord{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" id=" + mSyncId); + sb.append(" type=" + mType); + sb.append(" flags=" + mFlags); + sb.append('}'); + return sb.toString(); + } + + private static boolean reportIfNotTop(WindowContainer wc) { + // Organized tasks need to be reported anyways because Core won't show() their surfaces + // and we can't rely on onTaskAppeared because it isn't in sync. + // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN. + return wc.isOrganized(); + } + + /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */ + private static int getChildDepth(WindowContainer child, WindowContainer ancestor) { + WindowContainer parent = child; + int depth = 0; + while (parent != null) { + if (parent == ancestor) { + return depth; + } + parent = parent.getParent(); + ++depth; + } + return -1; + } + + private static @TransitionInfo.TransitionMode int getModeFor(WindowContainer wc) { + if (wc.isVisibleRequested()) { + final Task t = wc.asTask(); + if (t != null && t.getHasBeenVisible()) { + return TransitionInfo.TRANSIT_SHOW; + } + return TransitionInfo.TRANSIT_OPEN; + } + return TransitionInfo.TRANSIT_CLOSE; + } + + /** + * Under some conditions (eg. all visible targets within a parent container are transitioning + * the same way) the transition can be "promoted" to the parent container. This means an + * animation can play just on the parent rather than all the individual children. + * + * @return {@code true} if transition in target can be promoted to its parent. + */ + private static boolean canPromote( + WindowContainer target, ArraySet<WindowContainer> topTargets) { + final WindowContainer parent = target.getParent(); + if (parent == null || !parent.canCreateRemoteAnimationTarget()) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s", + parent == null ? "no parent" : ("parent can't be target " + parent)); + return false; + } + @TransitionInfo.TransitionMode int mode = TransitionInfo.TRANSIT_NONE; + // Go through all siblings of this target to see if any of them would prevent + // the target from promoting. + siblingLoop: + for (int i = parent.getChildCount() - 1; i >= 0; --i) { + final WindowContainer sibling = parent.getChildAt(i); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s", + sibling); + // Check if any topTargets are the sibling or within it + for (int j = topTargets.size() - 1; j >= 0; --j) { + final int depth = getChildDepth(topTargets.valueAt(j), sibling); + if (depth < 0) continue; + if (depth == 0) { + final int siblingMode = sibling.isVisibleRequested() + ? TransitionInfo.TRANSIT_OPEN : TransitionInfo.TRANSIT_CLOSE; + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " sibling is a top target with mode %s", + TransitionInfo.modeToString(siblingMode)); + if (mode == TransitionInfo.TRANSIT_NONE) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " no common mode yet, so set it"); + mode = siblingMode; + } else if (mode != siblingMode) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " SKIP: common mode mismatch. was %s", + TransitionInfo.modeToString(mode)); + return false; + } + continue siblingLoop; + } else { + // Sibling subtree may not be promotable. + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " SKIP: sibling contains top target %s", + topTargets.valueAt(j)); + return false; + } + } + // No other animations are playing in this sibling + if (sibling.isVisibleRequested()) { + // Sibling is visible but not animating, so no promote. + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " SKIP: sibling is visible but not part of transition"); + return false; + } + } + return true; + } + + /** + * Go through topTargets and try to promote (see {@link #canPromote}) one of them. + * + * @param topTargets set of just the top-most targets in the hierarchy of participants. + * @param targets all targets that will be sent to the player. + * @return {@code true} if something was promoted. + */ + private static boolean tryPromote(ArraySet<WindowContainer> topTargets, + ArrayMap<WindowContainer, ChangeInfo> targets) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " --- Start combine pass ---"); + // Go through each target until we find one that can be promoted. + targetLoop: + for (WindowContainer targ : topTargets) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", targ); + if (!canPromote(targ, topTargets)) { + continue; + } + final WindowContainer parent = targ.getParent(); + // No obstructions found to promotion, so promote + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " CAN PROMOTE: promoting to parent %s", parent); + final ChangeInfo parentInfo = new ChangeInfo(); + targets.put(parent, parentInfo); + // Go through all children of newly-promoted container and remove them from + // the top-targets. + for (int i = parent.getChildCount() - 1; i >= 0; --i) { + final WindowContainer child = parent.getChildAt(i); + int idx = targets.indexOfKey(child); + if (idx >= 0) { + if (reportIfNotTop(child)) { + targets.valueAt(idx).mParent = parent; + parentInfo.addChild(child); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " keep as target %s", child); + } else { + if (targets.valueAt(idx).mChildren != null) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " merging children in from %s: %s", child, + targets.valueAt(idx).mChildren); + parentInfo.addChildren(targets.valueAt(idx).mChildren); + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " remove from targets %s", child); + targets.removeAt(idx); + } + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " remove from topTargets %s", child); + topTargets.remove(child); + } + topTargets.add(parent); + return true; + } + return false; + } + + /** + * Find WindowContainers to be animated from a set of opening and closing apps. We will promote + * animation targets to higher level in the window hierarchy if possible. + */ + @VisibleForTesting + static TransitionInfo calculateTransitionInfo( + int type, Map<WindowContainer, ChangeInfo> participants) { + final TransitionInfo out = new TransitionInfo(type); + + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Start calculating TransitionInfo based on participants: %s", + new ArraySet<>(participants.keySet())); + + final ArraySet<WindowContainer> topTargets = new ArraySet<>(); + // The final animation targets which cannot promote to higher level anymore. + final ArrayMap<WindowContainer, ChangeInfo> targets = new ArrayMap<>(); + + final ArrayList<WindowContainer> tmpList = new ArrayList<>(); + + // Build initial set of top-level participants by removing any participants that are + // children of other participants or are otherwise invalid. + for (Map.Entry<WindowContainer, ChangeInfo> entry : participants.entrySet()) { + final WindowContainer wc = entry.getKey(); + // Don't include detached windows. + if (!wc.isAttached()) continue; + + final ChangeInfo changeInfo = entry.getValue(); + WindowContainer parent = wc.getParent(); + WindowContainer topParent = null; + // Keep track of always-report parents in bottom-to-top order + tmpList.clear(); + while (parent != null) { + if (participants.containsKey(parent)) { + topParent = parent; + } else if (reportIfNotTop(parent)) { + tmpList.add(parent); + } + parent = parent.getParent(); + } + if (topParent != null) { + // Add always-report parents along the way + parent = topParent; + for (int i = tmpList.size() - 1; i >= 0; --i) { + if (!participants.containsKey(tmpList.get(i))) { + final ChangeInfo info = new ChangeInfo(); + info.mParent = parent; + targets.put(tmpList.get(i), info); + } + parent = tmpList.get(i); + } + continue; + } + targets.put(wc, changeInfo); + topTargets.add(wc); + } + + // Populate children lists + for (int i = targets.size() - 1; i >= 0; --i) { + if (targets.valueAt(i).mParent != null) { + targets.get(targets.valueAt(i).mParent).addChild(targets.keyAt(i)); + } + } + + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " Initial targets: %s", new ArraySet<>(targets.keySet())); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Top targets: %s", topTargets); + + // Combine targets by repeatedly going through the topTargets to see if they can be + // promoted until there aren't any promotions possible. + while (tryPromote(topTargets, targets)) { + // Empty on purpose + } + + // Convert all the resolved ChangeInfos into a TransactionInfo object. + for (int i = targets.size() - 1; i >= 0; --i) { + final WindowContainer target = targets.keyAt(i); + final ChangeInfo info = targets.valueAt(i); + final TransitionInfo.Change change = new TransitionInfo.Change( + target.mRemoteToken.toWindowContainerToken(), target.getSurfaceControl()); + if (info.mParent != null) { + change.setParent(info.mParent.mRemoteToken.toWindowContainerToken()); + } + change.setMode(getModeFor(target)); + out.addChange(change); + } + + return out; + } + + static Transition fromBinder(IBinder binder) { + return (Transition) binder; + } + + @VisibleForTesting + static class ChangeInfo { + WindowContainer mParent; + ArraySet<WindowContainer> mChildren; + // TODO(shell-transitions): other tracking like before state and bounds + void addChild(@NonNull WindowContainer wc) { + if (mChildren == null) { + mChildren = new ArraySet<>(); + } + mChildren.add(wc); + } + void addChildren(@NonNull ArraySet<WindowContainer> wcs) { + if (mChildren == null) { + mChildren = new ArraySet<>(); + } + mChildren.addAll(wcs); + } + } +} diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java new file mode 100644 index 000000000000..d102c19bfff9 --- /dev/null +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -0,0 +1,225 @@ +/* + * 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.wm; + +import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_TASK_CLOSE; +import static android.view.WindowManager.TRANSIT_TASK_OPEN; +import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND; +import static android.view.WindowManager.TRANSIT_TASK_TO_BACK; +import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; +import android.view.WindowManager; +import android.window.ITransitionPlayer; + +import com.android.internal.protolog.ProtoLogGroup; +import com.android.internal.protolog.common.ProtoLog; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Handles all the aspects of recording and synchronizing transitions. + */ +class TransitionController { + private static final String TAG = "TransitionController"; + + private static final int[] SUPPORTED_LEGACY_TRANSIT_TYPES = {TRANSIT_TASK_OPEN, + TRANSIT_TASK_CLOSE, TRANSIT_TASK_TO_FRONT, TRANSIT_TASK_TO_BACK, + TRANSIT_TASK_OPEN_BEHIND, TRANSIT_KEYGUARD_GOING_AWAY}; + static { + Arrays.sort(SUPPORTED_LEGACY_TRANSIT_TYPES); + } + + final BLASTSyncEngine mSyncEngine = new BLASTSyncEngine(); + private ITransitionPlayer mTransitionPlayer; + private final IBinder.DeathRecipient mTransitionPlayerDeath = () -> mTransitionPlayer = null; + final ActivityTaskManagerService mAtm; + + /** Currently playing transitions. When finished, records are removed from this list. */ + private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>(); + + /** + * The transition currently being constructed (collecting participants). + * TODO(shell-transitions): When simultaneous transitions are supported, merge this with + * mPlayingTransitions. + */ + private Transition mCollectingTransition = null; + + TransitionController(ActivityTaskManagerService atm) { + mAtm = atm; + } + + /** @see #createTransition(int, int) */ + @NonNull + Transition createTransition(int type) { + return createTransition(type, 0 /* flags */); + } + + /** + * Creates a transition. It can immediately collect participants. + */ + @NonNull + Transition createTransition(@WindowManager.TransitionType int type, + @WindowManager.TransitionFlags int flags) { + if (mCollectingTransition != null) { + throw new IllegalStateException("Simultaneous transitions not supported yet."); + } + mCollectingTransition = new Transition(type, flags, this); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", + mCollectingTransition); + return mCollectingTransition; + } + + void registerTransitionPlayer(@Nullable ITransitionPlayer player) { + try { + if (mTransitionPlayer != null) { + mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0); + mTransitionPlayer = null; + } + player.asBinder().linkToDeath(mTransitionPlayerDeath, 0); + mTransitionPlayer = player; + } catch (RemoteException e) { + throw new RuntimeException("Unable to set transition player"); + } + } + + @Nullable ITransitionPlayer getTransitionPlayer() { + return mTransitionPlayer; + } + + boolean isShellTransitionsEnabled() { + return mTransitionPlayer != null; + } + + /** @return {@code true} if a transition is running */ + boolean inTransition() { + // TODO(shell-transitions): eventually properly support multiple + return mCollectingTransition != null || !mPlayingTransitions.isEmpty(); + } + + /** @return {@code true} if wc is in a participant subtree */ + boolean inTransition(@NonNull WindowContainer wc) { + if (mCollectingTransition != null && mCollectingTransition.mParticipants.containsKey(wc)) { + return true; + } + for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { + for (WindowContainer p = wc; p != null; p = p.getParent()) { + if (mPlayingTransitions.get(i).mParticipants.containsKey(p)) { + return true; + } + } + } + return false; + } + + /** + * Creates a transition and asks the TransitionPlayer (Shell) to start it. + * @return the created transition. Collection can start immediately. + */ + @NonNull + Transition requestTransition(@WindowManager.TransitionType int type) { + return requestTransition(type, 0 /* flags */); + } + + /** @see #requestTransition */ + @NonNull + Transition requestTransition(@WindowManager.TransitionType int type, + @WindowManager.TransitionFlags int flags) { + if (mTransitionPlayer == null) { + throw new IllegalStateException("Shell Transitions not enabled"); + } + final Transition transition = createTransition(type, flags); + try { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Requesting StartTransition: %s", transition); + mTransitionPlayer.requestStartTransition(type, transition); + } catch (RemoteException e) { + Slog.e(TAG, "Error requesting transition", e); + transition.start(); + } + return transition; + } + + /** + * Temporary adapter that converts the legacy AppTransition's prepareAppTransition call into + * a Shell transition request. If shell transitions are enabled, this will take priority in + * handling transition types that it supports. All other transitions will be ignored and thus + * be handled by the legacy apptransition system. This allows both worlds to live in tandem + * during migration. + * + * @return {@code true} if the transition is handled. + */ + boolean adaptLegacyPrepare(@WindowManager.TransitionType int transit, + @WindowManager.TransitionFlags int flags, boolean forceOverride) { + if (!isShellTransitionsEnabled() + || Arrays.binarySearch(SUPPORTED_LEGACY_TRANSIT_TYPES, transit) < 0) { + return false; + } + if (inTransition()) { + if (AppTransition.isKeyguardTransit(transit)) { + // TODO(shell-transitions): add to flags + } else if (forceOverride) { + // TODO(shell-transitions): sort out these flags + } else if (transit == TRANSIT_CRASHING_ACTIVITY_CLOSE) { + // TODO(shell-transitions): record crashing + } + } else { + requestTransition(transit, flags); + } + return true; + } + + /** @see Transition#collect */ + void collect(@NonNull WindowContainer wc) { + if (mCollectingTransition == null) return; + mCollectingTransition.collect(wc); + } + + /** @see Transition#setReady */ + void setReady() { + if (mCollectingTransition == null) return; + mCollectingTransition.setReady(); + } + + /** @see Transition#finishTransition */ + void finishTransition(@NonNull IBinder token) { + final Transition record = Transition.fromBinder(token); + if (record == null || !mPlayingTransitions.contains(record)) { + Slog.e(TAG, "Trying to finish a non-playing transition " + token); + return; + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record); + mPlayingTransitions.remove(record); + record.finishTransition(); + } + + void moveToPlaying(Transition transition) { + if (transition != mCollectingTransition) { + throw new IllegalStateException("Trying to move non-collecting transition to playing"); + } + mCollectingTransition = null; + mPlayingTransitions.add(transition); + } + +} diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 2b93080a8dad..2ece30d24b3a 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -417,7 +417,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< void setInitialSurfaceControlProperties(SurfaceControl.Builder b) { setSurfaceControl(b.setCallsite("WindowContainer.setInitialSurfaceControlProperties").build()); - getSyncTransaction().show(mSurfaceControl); + if (showSurfaceOnCreation()) { + getSyncTransaction().show(mSurfaceControl); + } onSurfaceShown(getSyncTransaction()); updateSurfacePositionNonOrganized(); } @@ -999,6 +1001,21 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** + * Is this window's surface needed? This is almost like isVisible, except when participating + * in a transition, this will reflect the final visibility while isVisible won't change until + * the transition is finished. + */ + boolean isVisibleRequested() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = mChildren.get(i); + if (child.isVisibleRequested()) { + return true; + } + } + return false; + } + + /** * Called when the visibility of a child is asked to change. This is before visibility actually * changes (eg. a transition animation might play out first). */ @@ -2816,6 +2833,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return false; } + /** + * @return {@code true} if this container's surface should be shown when it is created. + */ + boolean showSurfaceOnCreation() { + return true; + } + static WindowContainer fromBinder(IBinder binder) { return RemoteToken.fromBinder(binder).getContainer(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index d9c574ccc64c..19cfcb21c8ac 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -411,7 +411,7 @@ public class WindowManagerService extends IWindowManager.Stub * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY */ static boolean sDisableCustomTaskAnimationProperty = - SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, false); + SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true); private static final String DISABLE_TRIPLE_BUFFERING_PROPERTY = "ro.sf.disable_triple_buffer"; @@ -665,6 +665,8 @@ public class WindowManagerService extends IWindowManager.Stub // Whether the system should use BLAST for ViewRootImpl final boolean mUseBLAST; + // Whether to enable BLASTSyncEngine Transaction passing. + final boolean mUseBLASTSync = false; int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; Rect mDockedStackCreateBounds; @@ -864,8 +866,7 @@ public class WindowManagerService extends IWindowManager.Stub void updateSystemUiSettings() { boolean changed; synchronized (mGlobalLock) { - changed = ImmersiveModeConfirmation.loadSetting(mCurrentUserId, mContext) - || PolicyControl.reloadFromSetting(mContext); + changed = ImmersiveModeConfirmation.loadSetting(mCurrentUserId, mContext); } if (changed) { updateRotation(false /* alwaysSendConfiguration */, false /* forceRelayout */); @@ -1374,9 +1375,8 @@ public class WindowManagerService extends IWindowManager.Stub return false; } - public int addWindow(Session session, IWindow client, int seq, - LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, - Rect outContentInsets, Rect outStableInsets, + public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility, + int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls, int requestUserId) { @@ -1557,7 +1557,7 @@ public class WindowManagerService extends IWindowManager.Stub } final WindowState win = new WindowState(this, session, client, token, parentWindow, - appOp[0], seq, attrs, viewVisibility, session.mUid, userId, + appOp[0], attrs, viewVisibility, session.mUid, userId, session.mCanAddInternalSystemWindow); if (win.mDeathRecipient == null) { // Client has apparently died, so there is no reason to @@ -2099,7 +2099,7 @@ public class WindowManagerService extends IWindowManager.Stub == PackageManager.PERMISSION_GRANTED; } - public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams attrs, + public int relayoutWindow(Session session, IWindow client, LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, long frameNumber, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, @@ -2131,7 +2131,7 @@ public class WindowManagerService extends IWindowManager.Stub win.finishSeamlessRotation(false /* timeout */); } - if (win.useBLASTSync()) { + if (mUseBLASTSync && win.useBLASTSync()) { result |= RELAYOUT_RES_BLAST_SYNC; } @@ -2141,17 +2141,15 @@ public class WindowManagerService extends IWindowManager.Stub if (attrs != null) { displayPolicy.adjustWindowParamsLw(win, attrs, pid, uid); win.mToken.adjustWindowParams(win, attrs); - // if they don't have the permission, mask out the status bar bits - if (seq == win.mSeq) { - int systemUiVisibility = attrs.systemUiVisibility - | attrs.subtreeSystemUiVisibility; - if ((systemUiVisibility & DISABLE_MASK) != 0) { - if (!hasStatusBarPermission(pid, uid)) { - systemUiVisibility &= ~DISABLE_MASK; - } + int systemUiVisibility = attrs.systemUiVisibility + | attrs.subtreeSystemUiVisibility; + if ((systemUiVisibility & DISABLE_MASK) != 0) { + // if they don't have the permission, mask out the status bar bits + if (!hasStatusBarPermission(pid, uid)) { + systemUiVisibility &= ~DISABLE_MASK; } - win.mSystemUiVisibility = systemUiVisibility; } + win.mSystemUiVisibility = systemUiVisibility; if (win.mAttrs.type != attrs.type) { throw new IllegalArgumentException( "Window type can not be changed after the window is added."); @@ -2427,8 +2425,8 @@ public class WindowManagerService extends IWindowManager.Stub Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } if (winAnimator.mSurfaceController != null) { - outSurfaceSize.set(winAnimator.mSurfaceController.getWidth(), - winAnimator.mSurfaceController.getHeight()); + win.calculateSurfaceBounds(win.getAttrs(), mTmpRect); + outSurfaceSize.set(mTmpRect.width(), mTmpRect.height()); } getInsetsSourceControls(win, outActiveControls); } @@ -2464,7 +2462,10 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mAttrs.type == TYPE_APPLICATION_STARTING) { transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE; } - if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) { + if (mAtmService.getTransitionController().inTransition(win)) { + focusMayChange = true; + win.mAnimatingExit = true; + } else if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) { focusMayChange = true; win.mAnimatingExit = true; } else if (win.isAnimating(TRANSITION | PARENTS)) { @@ -5131,6 +5132,10 @@ public class WindowManagerService extends IWindowManager.Stub return mUseBLAST; } + public boolean useBLASTSync() { + return mUseBLASTSync; + } + @Override public void getInitialDisplaySize(int displayId, Point size) { synchronized (mGlobalLock) { @@ -5731,31 +5736,13 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void statusBarVisibilityChanged(int displayId, int visibility) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Caller does not hold permission " - + android.Manifest.permission.STATUS_BAR); - } - - synchronized (mGlobalLock) { - final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - if (displayContent != null) { - displayContent.statusBarVisibilityChanged(visibility); - } else { - Slog.w(TAG, "statusBarVisibilityChanged with invalid displayId=" + displayId); - } - } - } - - @Override public void hideTransientBars(int displayId) { mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.STATUS_BAR, "hideTransientBars()"); synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); if (displayContent != null) { - displayContent.hideTransientBars(); + displayContent.getInsetsPolicy().hideTransient(); } else { Slog.w(TAG, "hideTransientBars with invalid displayId=" + displayId); } @@ -6112,7 +6099,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (inputMethodControlTarget != null) { pw.print(" inputMethodControlTarget in display# "); pw.print(displayId); - pw.print(' '); pw.println(inputMethodControlTarget.getWindow()); + pw.print(' '); pw.println(inputMethodControlTarget); } }); pw.print(" mInTouchMode="); pw.println(mInTouchMode); @@ -6164,7 +6151,6 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mRecentsAnimationController="); pw.println(mRecentsAnimationController); mRecentsAnimationController.dump(pw, " "); } - PolicyControl.dump(" ", pw); } } @@ -8270,4 +8256,14 @@ public class WindowManagerService extends IWindowManager.Stub embeddedWindow.getName(), grantFocus); } } + + @Override + public void holdLock(int durationMs) { + mContext.enforceCallingPermission( + Manifest.permission.INJECT_EVENTS, "holdLock requires shell identity"); + + synchronized (mGlobalLock) { + SystemClock.sleep(durationMs); + } + } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 999181dc486c..f1641cdfcf67 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -26,6 +26,8 @@ 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; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.WindowConfiguration; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -40,6 +42,7 @@ import android.view.Surface; import android.view.SurfaceControl; import android.window.IDisplayAreaOrganizerController; import android.window.ITaskOrganizerController; +import android.window.ITransitionPlayer; import android.window.IWindowContainerTransactionCallback; import android.window.IWindowOrganizerController; import android.window.WindowContainerToken; @@ -88,21 +91,85 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final TaskOrganizerController mTaskOrganizerController; final DisplayAreaOrganizerController mDisplayAreaOrganizerController; + final TransitionController mTransitionController; + WindowOrganizerController(ActivityTaskManagerService atm) { mService = atm; mGlobalLock = atm.mGlobalLock; mTaskOrganizerController = new TaskOrganizerController(mService); mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService); + mTransitionController = new TransitionController(atm); + } + + TransitionController getTransitionController() { + return mTransitionController; } @Override public void applyTransaction(WindowContainerTransaction t) { - applySyncTransaction(t, null /*callback*/); + applyTransaction(t, null /*callback*/, null /*transition*/); } @Override public int applySyncTransaction(WindowContainerTransaction t, IWindowContainerTransactionCallback callback) { + return applyTransaction(t, callback, null /*transition*/); + } + + @Override + public IBinder startTransition(int type, @Nullable IBinder transitionToken, + @Nullable WindowContainerTransaction t) { + enforceStackPermission("startTransition()"); + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + Transition transition = Transition.fromBinder(transitionToken); + if (transition == null) { + if (type < 0) { + throw new IllegalArgumentException("Can't create transition with no type"); + } + transition = mTransitionController.createTransition(type); + } + transition.start(); + if (t == null) { + t = new WindowContainerTransaction(); + } + applyTransaction(t, null /*callback*/, transition); + return transition; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public int finishTransition(@NonNull IBinder transitionToken, + @Nullable WindowContainerTransaction t, + @Nullable IWindowContainerTransactionCallback callback) { + enforceStackPermission("finishTransition()"); + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + int syncId = -1; + if (t != null) { + syncId = applyTransaction(t, callback, null /*transition*/); + } + getTransitionController().finishTransition(transitionToken); + return syncId; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * @param callback If non-null, this will be a sync-transaction. + * @param transition A transition to collect changes into. + * @return a BLAST sync-id if this is a non-transition, sync transaction. + */ + private int applyTransaction(@NonNull WindowContainerTransaction t, + @Nullable IWindowContainerTransactionCallback callback, + @Nullable Transition transition) { enforceStackPermission("applySyncTransaction()"); int syncId = -1; if (t == null) { @@ -152,6 +219,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } int containerEffect = applyWindowContainerChange(wc, entry.getValue()); + if (transition != null) transition.collect(wc); effects |= containerEffect; // Lifecycle changes will trigger ensureConfig for everything. @@ -173,6 +241,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub addToSyncSet(syncId, wc); } effects |= sanitizeAndApplyHierarchyOp(wc, hop); + if (transition != null) { + transition.collect(wc); + if (hop.isReparent() && hop.getNewParent() != null) { + transition.collect(WindowContainer.fromBinder(hop.getNewParent())); + } + } } // Queue-up bounds-change transactions for tasks which are now organized. Do // this after hierarchy ops so we have the final organized state. @@ -512,6 +586,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return true; } + @Override + public void registerTransitionPlayer(ITransitionPlayer player) { + enforceStackPermission("registerTransitionPlayer()"); + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + mTransitionController.registerTransitionPlayer(player); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private void enforceStackPermission(String func) { mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func); } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 268281b7a880..4b8a398582f3 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -221,6 +221,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio @Nullable private BackgroundActivityStartCallback mBackgroundActivityStartCallback; + /** The state for oom-adjustment calculation. */ + private final OomScoreReferenceState mOomRefState; + public WindowProcessController(@NonNull ActivityTaskManagerService atm, ApplicationInfo info, String name, int uid, int userId, Object owner, WindowProcessListener listener) { mInfo = info; @@ -232,6 +235,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio mAtm = atm; mDisplayId = INVALID_DISPLAY; mBackgroundActivityStartCallback = mAtm.getBackgroundActivityStartCallback(); + mOomRefState = new OomScoreReferenceState(this); boolean isSysUiPackage = info.packageName.equals( mAtm.getSysUiServiceComponentLocked().getPackageName()); @@ -688,15 +692,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio @HotPath(caller = HotPath.OOM_ADJUSTMENT) public boolean hasVisibleActivities() { - synchronized (mAtm.mGlobalLockWithoutBoost) { - for (int i = mActivities.size() - 1; i >= 0; --i) { - final ActivityRecord r = mActivities.get(i); - if (r.mVisibleRequested) { - return true; - } - } - } - return false; + return (mOomRefState.mActivityStateFlags & OomScoreReferenceState.FLAG_IS_VISIBLE) != 0; } @HotPath(caller = HotPath.LRU_UPDATE) @@ -991,6 +987,34 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio mHostActivities.remove(r); } + private static class OomScoreReferenceState extends RootWindowContainer.LockedScheduler { + private static final int FLAG_IS_VISIBLE = 0x10000000; + private static final int FLAG_IS_PAUSING = 0x20000000; + private static final int FLAG_IS_STOPPING = 0x40000000; + private static final int FLAG_IS_STOPPING_FINISHING = 0x80000000; + /** @see Task#mLayerRank */ + private static final int MASK_MIN_TASK_LAYER = 0x0000ffff; + + private final WindowProcessController mOwner; + boolean mChanged; + + /** + * The higher 16 bits are the activity states, and the lower 16 bits are the task layer + * rank. This field is written by window manager and read by activity manager. + */ + volatile int mActivityStateFlags = MASK_MIN_TASK_LAYER; + + OomScoreReferenceState(WindowProcessController owner) { + super(owner.mAtm); + mOwner = owner; + } + + @Override + public void execute() { + mOwner.computeOomScoreReferenceStateIfNeeded(); + } + } + public interface ComputeOomAdjCallback { void onVisibleActivity(); void onPausedActivity(); @@ -998,64 +1022,102 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio void onOtherActivity(); } + /** + * Returns the minimum task layer rank. It should only be called if {@link #hasActivities} + * returns {@code true}. + */ @HotPath(caller = HotPath.OOM_ADJUSTMENT) - public int computeOomAdjFromActivities(int minTaskLayer, ComputeOomAdjCallback callback) { + public int computeOomAdjFromActivities(ComputeOomAdjCallback callback) { + final int flags = mOomRefState.mActivityStateFlags; + if ((flags & OomScoreReferenceState.FLAG_IS_VISIBLE) != 0) { + callback.onVisibleActivity(); + } else if ((flags & OomScoreReferenceState.FLAG_IS_PAUSING) != 0) { + callback.onPausedActivity(); + } else if ((flags & OomScoreReferenceState.FLAG_IS_STOPPING) != 0) { + callback.onStoppingActivity( + (flags & OomScoreReferenceState.FLAG_IS_STOPPING_FINISHING) != 0); + } else { + callback.onOtherActivity(); + } + return flags & OomScoreReferenceState.MASK_MIN_TASK_LAYER; + } + + void computeOomScoreReferenceStateIfNeeded() { + if (!mOomRefState.mChanged) { + return; + } + mOomRefState.mChanged = false; + // Since there could be more than one activities in a process record, we don't need to // compute the OomAdj with each of them, just need to find out the activity with the // "best" state, the order would be visible, pausing, stopping... Task.ActivityState best = DESTROYED; boolean finishing = true; boolean visible = false; - synchronized (mAtm.mGlobalLockWithoutBoost) { - final int activitiesSize = mActivities.size(); - for (int j = 0; j < activitiesSize; j++) { - final ActivityRecord r = mActivities.get(j); - if (r.app != this) { - Log.e(TAG, "Found activity " + r + " in proc activity list using " + r.app - + " instead of expected " + this); - if (r.app == null || (r.app.mUid == mUid)) { - // Only fix things up when they look sane - r.setProcess(this); - } else { - continue; - } - } - if (r.mVisibleRequested) { - final Task task = r.getTask(); - if (task != null && minTaskLayer > 0) { - final int layer = task.mLayerRank; - if (layer >= 0 && minTaskLayer > layer) { - minTaskLayer = layer; - } - } - visible = true; - // continue the loop, in case there are multiple visible activities in - // this process, we'd find out the one with the minimal layer, thus it'll - // get a higher adj score. + int minTaskLayer = Integer.MAX_VALUE; + for (int i = mActivities.size() - 1; i >= 0; i--) { + final ActivityRecord r = mActivities.get(i); + if (r.app != this) { + Slog.e(TAG, "Found activity " + r + " in proc activity list using " + r.app + + " instead of expected " + this); + if (r.app == null || (r.app.mUid == mUid)) { + // Only fix things up when they look valid. + r.setProcess(this); } else { - if (best != PAUSING && best != PAUSED) { - if (r.isState(PAUSING, PAUSED)) { - best = PAUSING; - } else if (r.isState(STOPPING)) { - best = STOPPING; - // Not "finishing" if any of activity isn't finishing. - finishing &= r.finishing; - } + continue; + } + } + if (r.mVisibleRequested) { + final Task task = r.getTask(); + if (task != null && minTaskLayer > 0) { + final int layer = task.mLayerRank; + if (layer >= 0 && minTaskLayer > layer) { + minTaskLayer = layer; } } + visible = true; + // continue the loop, in case there are multiple visible activities in + // this process, we'd find out the one with the minimal layer, thus it'll + // get a higher adj score. + } else if (best != PAUSING && best != PAUSED) { + if (r.isState(PAUSING, PAUSED)) { + best = PAUSING; + } else if (r.isState(STOPPING)) { + best = STOPPING; + // Not "finishing" if any of activity isn't finishing. + finishing &= r.finishing; + } + } + + int stateFlags = minTaskLayer & OomScoreReferenceState.MASK_MIN_TASK_LAYER; + if (visible) { + stateFlags |= OomScoreReferenceState.FLAG_IS_VISIBLE; + } else if (best == PAUSING) { + stateFlags |= OomScoreReferenceState.FLAG_IS_PAUSING; + } else if (best == STOPPING) { + stateFlags |= OomScoreReferenceState.FLAG_IS_STOPPING; + if (finishing) { + stateFlags |= OomScoreReferenceState.FLAG_IS_STOPPING_FINISHING; + } } + mOomRefState.mActivityStateFlags = stateFlags; } - if (visible) { - callback.onVisibleActivity(); - } else if (best == PAUSING) { - callback.onPausedActivity(); - } else if (best == STOPPING) { - callback.onStoppingActivity(finishing); - } else { - callback.onOtherActivity(); + } + + void invalidateOomScoreReferenceState(boolean computeNow) { + mOomRefState.mChanged = true; + if (computeNow) { + computeOomScoreReferenceStateIfNeeded(); + return; } + mOomRefState.scheduleIfNeeded(); + } - return minTaskLayer; + /** Called when the process has some oom related changes and it is going to update oom-adj. */ + private void prepareOomAdjustment() { + mAtm.mRootWindowContainer.rankTaskLayersIfNeeded(); + // The task layer may not change but the activity state in the same task may change. + computeOomScoreReferenceStateIfNeeded(); } public int computeRelaunchReason() { @@ -1097,6 +1159,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio if (addPendingTopUid) { mAtm.mAmInternal.addPendingTopUid(mUid, mPid); } + if (updateOomAdj) { + prepareOomAdjustment(); + } // Posting on handler so WM lock isn't held when we call into AM. final Message m = PooledLambda.obtainMessage(WindowProcessListener::updateProcessInfo, mListener, updateServiceConnectionActivities, activityChange, updateOomAdj); @@ -1158,6 +1223,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio if (topProcessState == ActivityManager.PROCESS_STATE_TOP) { mAtm.mAmInternal.addPendingTopUid(mUid, mPid); } + prepareOomAdjustment(); // Posting the message at the front of queue so WM lock isn't held when we call into AM, // and the process state of starting activity can be updated quicker which will give it a // higher scheduling group. diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 1f7457c088c5..8f42b3f154f7 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -302,7 +302,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final boolean mIsImWindow; final boolean mIsWallpaper; private final boolean mIsFloatingLayer; - int mSeq; int mViewVisibility; int mSystemUiVisibility; @@ -834,10 +833,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, - WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a, - int viewVisibility, int ownerId, int showUserId, - boolean ownerCanAddInternalSystemWindow) { - this(service, s, c, token, parentWindow, appOp, seq, a, viewVisibility, ownerId, showUserId, + WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility, + int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow) { + this(service, s, c, token, parentWindow, appOp, a, viewVisibility, ownerId, showUserId, ownerCanAddInternalSystemWindow, new PowerManagerWrapper() { @Override public void wakeUp(long time, @WakeReason int reason, String details) { @@ -852,9 +850,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, - WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a, - int viewVisibility, int ownerId, int showUserId, - boolean ownerCanAddInternalSystemWindow, PowerManagerWrapper powerManagerWrapper) { + WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility, + int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow, + PowerManagerWrapper powerManagerWrapper) { super(service); mSession = s; mClient = c; @@ -871,7 +869,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mPolicy = mWmService.mPolicy; mContext = mWmService.mContext; DeathRecipient deathRecipient = new DeathRecipient(); - mSeq = seq; mPowerManagerWrapper = powerManagerWrapper; mForceSeamlesslyRotate = token.mRoundedCornerOverlay; if (DEBUG) { @@ -1412,15 +1409,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Add a window that is using blastSync to the resizing list if it hasn't been reported // already. This because the window is waiting on a finishDrawing from the client. if (didFrameInsetsChange - || winAnimator.mSurfaceResized || configChanged || dragResizingChanged || mReportOrientationChanged || shouldSendRedrawForSync()) { ProtoLog.v(WM_DEBUG_RESIZE, - "Resize reasons for w=%s: %s surfaceResized=%b configChanged=%b " + "Resize reasons for w=%s: %s configChanged=%b " + "dragResizingChanged=%b reportOrientationChanged=%b", - this, mWindowFrames.getInsetsChangedInfo(), winAnimator.mSurfaceResized, + this, mWindowFrames.getInsetsChangedInfo(), configChanged, dragResizingChanged, mReportOrientationChanged); // If it's a dead window left on screen, and the configuration changed, there is nothing @@ -1687,6 +1683,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP || mControllableInsetProvider.isClientVisible()); } + @Override + boolean isVisibleRequested() { + return isVisible(); + } + /** * Ensures that all the policy visibility bits are set. * @return {@code true} if all flags about visiblity are set @@ -1775,7 +1776,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } final ActivityRecord atoken = mActivityRecord; if (atoken != null) { - return ((!isParentWindowHidden() && atoken.mVisibleRequested) + return ((!isParentWindowHidden() && atoken.isVisible()) || isAnimating(TRANSITION | PARENTS)); } return !isParentWindowHidden() || isAnimating(TRANSITION | PARENTS); @@ -3636,7 +3637,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // that may cause WINDOW_FREEZE_TIMEOUT because resizing the client keeps failing. mReportOrientationChanged = false; mDragResizingChangeReported = true; - mWinAnimator.mSurfaceResized = false; mWindowFrames.resetInsetsChanged(); final MergedConfiguration mergedConfiguration = mLastReportedConfiguration; @@ -4031,7 +4031,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.println(prefix + "mViewVisibility=0x" + Integer.toHexString(mViewVisibility) + " mHaveFrame=" + mHaveFrame + " mObscured=" + mObscured); - pw.println(prefix + "mSeq=" + mSeq + pw.println(prefix + " mSystemUiVisibility=0x" + Integer.toHexString(mSystemUiVisibility)); } if (!isVisibleByPolicy() || !mLegacyPolicyVisibilityAfterAnim || !mAppOpVisibility @@ -4955,93 +4955,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } /** - * Calculate the window crop according to system decor policy. In general this is - * the system decor rect (see #calculateSystemDecorRect), but we also have some - * special cases. This rectangle is in screen space. - */ - void calculatePolicyCrop(Rect policyCrop) { - final DisplayContent displayContent = getDisplayContent(); - - if (!displayContent.isDefaultDisplay && !displayContent.supportsSystemDecorations()) { - // On a different display there is no system decor. Crop the window - // by the screen boundaries. - final DisplayInfo displayInfo = getDisplayInfo(); - policyCrop.set(0, 0, mWindowFrames.mCompatFrame.width(), - mWindowFrames.mCompatFrame.height()); - policyCrop.intersect(-mWindowFrames.mCompatFrame.left, -mWindowFrames.mCompatFrame.top, - displayInfo.logicalWidth - mWindowFrames.mCompatFrame.left, - displayInfo.logicalHeight - mWindowFrames.mCompatFrame.top); - } else if (skipDecorCrop()) { - // Windows without policy decor aren't cropped. - policyCrop.set(0, 0, mWindowFrames.mCompatFrame.width(), - mWindowFrames.mCompatFrame.height()); - } else { - // Crop to the system decor specified by policy. - calculateSystemDecorRect(policyCrop); - } - } - - /** - * The system decor rect is the region of the window which is not covered - * by system decorations. - */ - private void calculateSystemDecorRect(Rect systemDecorRect) { - final Rect decorRect = mWindowFrames.mDecorFrame; - final int width = mWindowFrames.mFrame.width(); - final int height = mWindowFrames.mFrame.height(); - - final int left = mWindowFrames.mFrame.left; - final int top = mWindowFrames.mFrame.top; - - // Initialize the decor rect to the entire frame. - if (isDockedResizing()) { - // If we are resizing with the divider, the task bounds might be smaller than the - // stack bounds. The system decor is used to clip to the task bounds, which we don't - // want in this case in order to avoid holes. - // - // We take care to not shrink the width, for surfaces which are larger than - // the display region. Of course this area will not eventually be visible - // but if we truncate the width now, we will calculate incorrectly - // when adjusting to the stack bounds. - final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo(); - systemDecorRect.set(0, 0, - Math.max(width, displayInfo.logicalWidth), - Math.max(height, displayInfo.logicalHeight)); - } else { - systemDecorRect.set(0, 0, width, height); - } - - // If a freeform window is animating from a position where it would be cutoff, it would be - // cutoff during the animation. We don't want that, so for the duration of the animation - // we ignore the decor cropping and depend on layering to position windows correctly. - - // We also ignore cropping when the window is currently being drag resized in split screen - // to prevent issues with the crop for screenshot. - final boolean cropToDecor = - !(inFreeformWindowingMode() && isAnimatingLw()) && !isDockedResizing(); - if (cropToDecor) { - // Intersect with the decor rect, offsetted by window position. - systemDecorRect.intersect(decorRect.left - left, decorRect.top - top, - decorRect.right - left, decorRect.bottom - top); - } - - // If size compatibility is being applied to the window, the - // surface is scaled relative to the screen. Also apply this - // scaling to the crop rect. We aren't using the standard rect - // scale function because we want to round things to make the crop - // always round to a larger rect to ensure we don't crop too - // much and hide part of the window that should be seen. - if (mInvGlobalScale != 1.0f && inSizeCompatMode()) { - final float scale = mInvGlobalScale; - systemDecorRect.left = (int) (systemDecorRect.left * scale - 0.5f); - systemDecorRect.top = (int) (systemDecorRect.top * scale - 0.5f); - systemDecorRect.right = (int) ((systemDecorRect.right + 1) * scale - 0.5f); - systemDecorRect.bottom = (int) ((systemDecorRect.bottom + 1) * scale - 0.5f); - } - - } - - /** * Expand the given rectangle by this windows surface insets. This * takes you from the 'window size' to the 'surface size'. * The surface insets are positive in each direction, so we inset by @@ -5098,9 +5011,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // on the new one. This prevents blinking when we change elevation of freeform and // pinned windows. if (!mWinAnimator.tryChangeFormatInPlaceLocked()) { - mWinAnimator.preserveSurfaceLocked(); + mWinAnimator.preserveSurfaceLocked(getPendingTransaction()); result |= RELAYOUT_RES_SURFACE_CHANGED | RELAYOUT_RES_FIRST_TIME; + scheduleAnimation(); } } @@ -5116,9 +5030,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // to preserve and destroy windows which are attached to another, they // will keep their surface and its size may change over time. if (mHasSurface && !isChildWindow()) { - mWinAnimator.preserveSurfaceLocked(); + mWinAnimator.preserveSurfaceLocked(getPendingTransaction()); result |= RELAYOUT_RES_SURFACE_CHANGED | RELAYOUT_RES_FIRST_TIME; + scheduleAnimation(); } } final boolean freeformResizing = isDragResizing() @@ -5893,7 +5808,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } mNotifyBlastOnSurfacePlacement = true; - return mWinAnimator.finishDrawingLocked(null); + mWinAnimator.finishDrawingLocked(null); + // We always want to force a traversal after a finish draw for blast sync. + return true; } private void notifyBlastSyncTransaction() { @@ -5904,6 +5821,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return; } + final Task task = getTask(); + if (task != null) { + final SurfaceControl.Transaction t = task.getMainWindowSizeChangeTransaction(); + if (t != null) { + mBLASTSyncTransaction.merge(t); + } + task.setMainWindowSizeChangeTransaction(null); + } + // If localSyncId is >0 then we are syncing with children and will // invoke transaction ready from our own #transactionReady callback // we just need to signal our side of the sync (setReady). But if we @@ -5942,4 +5868,37 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void requestRedrawForSync() { mRedrawForSyncReported = false; } + + void calculateSurfaceBounds(WindowManager.LayoutParams attrs, Rect outSize) { + outSize.setEmpty(); + if ((attrs.flags & FLAG_SCALED) != 0) { + // For a scaled surface, we always want the requested size. + outSize.right = mRequestedWidth; + outSize.bottom = mRequestedHeight; + } else { + // When we're doing a drag-resizing, request a surface that's fullscreen size, + // so that we don't need to reallocate during the process. This also prevents + // buffer drops due to size mismatch. + if (isDragResizing()) { + final DisplayInfo displayInfo = getDisplayInfo(); + outSize.right = displayInfo.logicalWidth; + outSize.bottom = displayInfo.logicalHeight; + } else { + getCompatFrameSize(outSize); + } + } + + // This doesn't necessarily mean that there is an error in the system. The sizes might be + // incorrect, because it is before the first layout or draw. + if (outSize.width() < 1) { + outSize.right = 1; + } + if (outSize.height() < 1) { + outSize.bottom = 1; + } + + // Adjust for surface insets. + outSize.inset(-attrs.surfaceInsets.left, -attrs.surfaceInsets.top, + -attrs.surfaceInsets.right, -attrs.surfaceInsets.bottom); + } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 029c158814b3..9f653d6124b9 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -121,12 +121,6 @@ class WindowStateAnimator { boolean mAnimationIsEntrance; - /** - * Set when we have changed the size of the surface, to know that - * we must tell them application to resize (and thus redraw itself). - */ - boolean mSurfaceResized; - WindowSurfaceController mSurfaceController; private WindowSurfaceController mPendingDestroySurface; @@ -141,9 +135,6 @@ class WindowStateAnimator { float mAlpha = 0; float mLastAlpha = 0; - Rect mTmpClipRect = new Rect(); - Rect mLastClipRect = new Rect(); - Rect mLastFinalClipRect = new Rect(); Rect mTmpStackBounds = new Rect(); private Rect mTmpAnimatingBounds = new Rect(); private Rect mTmpSourceBounds = new Rect(); @@ -358,7 +349,7 @@ class WindowStateAnimator { return result; } - void preserveSurfaceLocked() { + void preserveSurfaceLocked(SurfaceControl.Transaction t) { if (mDestroyPreservedSurfaceUponRedraw) { // This could happen when switching the surface mode very fast. For example, // we preserved a surface when dragResizing changed to true. Then before the @@ -385,7 +376,7 @@ class WindowStateAnimator { // Our SurfaceControl is always at layer 0 within the parent Surface managed by // window-state. We want this old Surface to stay on top of the new one // until we do the swap, so we place it at a positive layer. - mSurfaceController.mSurfaceControl.setLayer(PRESERVED_SURFACE_LAYER); + t.setLayer(mSurfaceController.getClientViewRootSurface(), PRESERVED_SURFACE_LAYER); } mDestroyPreservedSurfaceUponRedraw = true; mSurfaceDestroyDeferred = true; @@ -462,7 +453,8 @@ class WindowStateAnimator { flags |= SurfaceControl.SKIP_SCREENSHOT; } - calculateSurfaceBounds(w, attrs, mTmpSize); + w.calculateSurfaceBounds(attrs, mTmpSize); + final int width = mTmpSize.width(); final int height = mTmpSize.height(); @@ -474,9 +466,6 @@ class WindowStateAnimator { + " format=" + attrs.format + " flags=" + flags); } - // We may abort, so initialize to defaults. - mLastClipRect.set(0, 0, 0, 0); - // Set up surface control with initial size. try { @@ -538,40 +527,6 @@ class WindowStateAnimator { return mSurfaceController; } - private void calculateSurfaceBounds(WindowState w, LayoutParams attrs, Rect outSize) { - outSize.setEmpty(); - if ((attrs.flags & FLAG_SCALED) != 0) { - // For a scaled surface, we always want the requested size. - outSize.right = w.mRequestedWidth; - outSize.bottom = w.mRequestedHeight; - } else { - // When we're doing a drag-resizing, request a surface that's fullscreen size, - // so that we don't need to reallocate during the process. This also prevents - // buffer drops due to size mismatch. - if (w.isDragResizing()) { - final DisplayInfo displayInfo = w.getDisplayInfo(); - outSize.right = displayInfo.logicalWidth; - outSize.bottom = displayInfo.logicalHeight; - } else { - w.getCompatFrameSize(outSize); - } - } - - // Something is wrong and SurfaceFlinger will not like this, try to revert to reasonable - // values. This doesn't necessarily mean that there is an error in the system. The sizes - // might be incorrect, because it is before the first layout or draw. - if (outSize.width() < 1) { - outSize.right = 1; - } - if (outSize.height() < 1) { - outSize.bottom = 1; - } - - // Adjust for surface insets. - outSize.inset(-attrs.surfaceInsets.left, -attrs.surfaceInsets.top, - -attrs.surfaceInsets.right, -attrs.surfaceInsets.bottom); - } - boolean hasSurface() { return mSurfaceController != null && mSurfaceController.hasSurface(); } @@ -680,78 +635,12 @@ class WindowStateAnimator { mDsDy = mWin.mGlobalScale; } - /** - * Calculate the window-space crop rect and fill clipRect. - * @return true if clipRect has been filled otherwise, no window space crop should be applied. - */ - private boolean calculateCrop(Rect clipRect) { - final WindowState w = mWin; - final DisplayContent displayContent = w.getDisplayContent(); - clipRect.setEmpty(); - - if (displayContent == null) { - return false; - } - - if (w.getWindowConfiguration().tasksAreFloating() - || WindowConfiguration.isSplitScreenWindowingMode(w.getWindowingMode())) { - return false; - } - - // During forced seamless rotation, the surface bounds get updated with the crop in the - // new rotation, which is not compatible with showing the surface in the old rotation. - // To work around that we disable cropping for such windows, as it is not necessary anyways. - if (w.mForceSeamlesslyRotate) { - return false; - } - - // If we're animating, the wallpaper should only - // be updated at the end of the animation. - if (w.mAttrs.type == TYPE_WALLPAPER) { - return false; - } - - if (DEBUG_WINDOW_CROP) Slog.d(TAG, - "Updating crop win=" + w + " mLastCrop=" + mLastClipRect); - - w.calculatePolicyCrop(mSystemDecorRect); - - if (DEBUG_WINDOW_CROP) Slog.d(TAG, "Applying decor to crop win=" + w + " mDecorFrame=" - + w.getDecorFrame() + " mSystemDecorRect=" + mSystemDecorRect); - - // We use the clip rect as provided by the tranformation for non-fullscreen windows to - // avoid premature clipping with the system decor rect. - clipRect.set(mSystemDecorRect); - if (DEBUG_WINDOW_CROP) Slog.d(TAG, "win=" + w + " Initial clip rect: " + clipRect); - - w.expandForSurfaceInsets(clipRect); - - // The clip rect was generated assuming (0,0) as the window origin, - // so we need to translate to match the actual surface coordinates. - clipRect.offset(w.mAttrs.surfaceInsets.left, w.mAttrs.surfaceInsets.top); - - if (DEBUG_WINDOW_CROP) Slog.d(TAG, - "win=" + w + " Clip rect after stack adjustment=" + clipRect); - - w.transformClipRectFromScreenToSurfaceSpace(clipRect); - - return true; - } - - private void applyCrop(Rect clipRect, boolean recoveringMemory) { - if (DEBUG_WINDOW_CROP) Slog.d(TAG, "applyCrop: win=" + mWin - + " clipRect=" + clipRect); - if (clipRect != null) { - if (!clipRect.equals(mLastClipRect)) { - mLastClipRect.set(clipRect); - mSurfaceController.setCropInTransaction(clipRect, recoveringMemory); - } - } else { - mSurfaceController.clearCropInTransaction(recoveringMemory); - } - } - private boolean shouldConsumeMainWindowSizeTransaction() { + // If we use BLASTSync we always consume the transaction when finishing + // the sync. + if (mService.useBLASTSync()) { + return false; + } // We only consume the transaction when the client is calling relayout // because this is the only time we know the frameNumber will be valid // due to the client renderer being paused. Put otherwise, only when @@ -777,31 +666,6 @@ class WindowStateAnimator { final LayoutParams attrs = mWin.getAttrs(); final Task task = w.getTask(); - calculateSurfaceBounds(w, attrs, mTmpSize); - - // Once relayout has been called at least once, we need to make sure - // we only resize the client surface during calls to relayout. For - // clients which use indeterminate measure specs (MATCH_PARENT), - // we may try and change their window size without a call to relayout. - // However, this would be unsafe, as the client may be in the middle - // of producing a frame at the old size, having just completed layout - // to find the surface size changed underneath it. - final boolean relayout = !w.mRelayoutCalled || w.mInRelayout; - if (relayout) { - mSurfaceResized = mSurfaceController.setBufferSizeInTransaction( - mTmpSize.width(), mTmpSize.height(), recoveringMemory); - } else { - mSurfaceResized = false; - } - // If we are undergoing seamless rotation, the surface has already - // been set up to persist at it's old location. We need to freeze - // updates until a resize occurs. - - Rect clipRect = null; - if (calculateCrop(mTmpClipRect)) { - clipRect = mTmpClipRect; - } - if (shouldConsumeMainWindowSizeTransaction()) { task.getMainWindowSizeChangeTask().getSurfaceControl().deferTransactionUntil( mWin.getClientViewRootSurface(), mWin.getFrameNumber()); @@ -816,6 +680,11 @@ class WindowStateAnimator { final Rect insets = attrs.surfaceInsets; + // getFrameNumber is only valid in the call-stack of relayoutWindow + // as this is the only-time we know the client renderer + // is paused. + final boolean relayout = !w.mRelayoutCalled || w.mInRelayout; + if (!w.mSeamlesslyRotated) { // Used to offset the WSA when stack position changes before a resize. int xOffset = mXOffset; @@ -838,12 +707,6 @@ class WindowStateAnimator { } xOffset = -mTmpPos.x; yOffset = -mTmpPos.y; - // Crop also needs to be extended so the bottom isn't cut off when the WSA - // position is moved. - if (clipRect != null) { - clipRect.right += mTmpPos.x; - clipRect.bottom += mTmpPos.y; - } } } if (!mIsWallpaper) { @@ -858,7 +721,6 @@ class WindowStateAnimator { // Wallpaper is already updated above when calling setWallpaperPositionAndScale so // we only need to consider the non-wallpaper case here. if (!mIsWallpaper) { - applyCrop(clipRect, recoveringMemory); mSurfaceController.setMatrixInTransaction( mDsDx * w.mHScale, mDtDx * w.mVScale, @@ -866,10 +728,6 @@ class WindowStateAnimator { mDsDy * w.mVScale, recoveringMemory); } } - - if (mSurfaceResized) { - mWin.getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; - } } /** @@ -1064,7 +922,6 @@ class WindowStateAnimator { mDtDy * mWin.mTmpMatrixArray[MSKEW_X] * mWin.mHScale, mDsDy * mWin.mTmpMatrixArray[MSCALE_Y] * mWin.mVScale, recoveringMemory); - applyCrop(null, recoveringMemory); } /** @@ -1255,7 +1112,6 @@ class WindowStateAnimator { void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - mLastClipRect.dumpDebug(proto, LAST_CLIP_RECT); if (mSurfaceController != null) { mSurfaceController.dumpDebug(proto, SURFACE); } @@ -1276,11 +1132,7 @@ class WindowStateAnimator { pw.print(prefix); pw.print(" mLastHidden="); pw.println(mLastHidden); pw.print(prefix); pw.print("mEnterAnimationPending=" + mEnterAnimationPending); pw.print(prefix); pw.print("mSystemDecorRect="); mSystemDecorRect.printShortString(pw); - pw.print(" mLastClipRect="); mLastClipRect.printShortString(pw); - if (!mLastFinalClipRect.isEmpty()) { - pw.print(" mLastFinalClipRect="); mLastFinalClipRect.printShortString(pw); - } pw.println(); } @@ -1288,8 +1140,7 @@ class WindowStateAnimator { pw.print(prefix); pw.print("mPendingDestroySurface="); pw.println(mPendingDestroySurface); } - if (mSurfaceResized || mSurfaceDestroyDeferred) { - pw.print(prefix); pw.print("mSurfaceResized="); pw.print(mSurfaceResized); + if (mSurfaceDestroyDeferred) { pw.print(" mSurfaceDestroyDeferred="); pw.println(mSurfaceDestroyDeferred); } if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) { diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 44462c33ccdf..30f6fa68396e 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -133,6 +133,7 @@ cc_defaults { "android.hardware.broadcastradio@1.0", "android.hardware.broadcastradio@1.1", "android.hardware.contexthub@1.0", + "android.hardware.gnss-cpp", "android.hardware.gnss@1.0", "android.hardware.gnss@1.1", "android.hardware.gnss@2.0", diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 17a05f3c7d1a..e39a3d1e9cb5 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -218,7 +218,7 @@ public: void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj); void setFocusedDisplay(JNIEnv* env, int32_t displayId); void setInputDispatchMode(bool enabled, bool frozen); - void setSystemUiVisibility(int32_t visibility); + void setSystemUiLightsOut(bool lightsOut); void setPointerSpeed(int32_t speed); void setInputDeviceEnabled(uint32_t deviceId, bool enabled); void setShowTouches(bool enabled); @@ -286,8 +286,8 @@ private: // Display size information. std::vector<DisplayViewport> viewports; - // System UI visibility. - int32_t systemUiVisibility; + // True if System UI is less noticeable. + bool systemUiLightsOut; // Pointer speed. int32_t pointerSpeed; @@ -339,7 +339,7 @@ NativeInputManager::NativeInputManager(jobject contextObj, { AutoMutex _l(mLock); - mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE; + mLocked.systemUiLightsOut = false; mLocked.pointerSpeed = 0; mLocked.pointerGesturesEnabled = true; mLocked.showTouches = false; @@ -366,8 +366,8 @@ void NativeInputManager::dump(std::string& dump) { } { AutoMutex _l(mLock); - dump += StringPrintf(INDENT "System UI Visibility: 0x%0" PRIx32 "\n", - mLocked.systemUiVisibility); + dump += StringPrintf(INDENT "System UI Lights Out: %s\n", + toString(mLocked.systemUiLightsOut)); dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed); dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n", toString(mLocked.pointerGesturesEnabled)); @@ -811,11 +811,11 @@ void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) { mInputManager->getDispatcher()->setInputDispatchMode(enabled, frozen); } -void NativeInputManager::setSystemUiVisibility(int32_t visibility) { +void NativeInputManager::setSystemUiLightsOut(bool lightsOut) { AutoMutex _l(mLock); - if (mLocked.systemUiVisibility != visibility) { - mLocked.systemUiVisibility = visibility; + if (mLocked.systemUiLightsOut != lightsOut) { + mLocked.systemUiLightsOut = lightsOut; updateInactivityTimeoutLocked(); } } @@ -826,9 +826,8 @@ void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) { return; } - bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN; - controller->setInactivityTimeout(lightsOut ? InactivityTimeout::SHORT - : InactivityTimeout::NORMAL); + controller->setInactivityTimeout(mLocked.systemUiLightsOut ? InactivityTimeout::SHORT + : InactivityTimeout::NORMAL); } void NativeInputManager::setPointerSpeed(int32_t speed) { @@ -1578,11 +1577,11 @@ static void nativeSetInputDispatchMode(JNIEnv* /* env */, im->setInputDispatchMode(enabled, frozen); } -static void nativeSetSystemUiVisibility(JNIEnv* /* env */, - jclass /* clazz */, jlong ptr, jint visibility) { +static void nativeSetSystemUiLightsOut(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, + jboolean lightsOut) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); - im->setSystemUiVisibility(visibility); + im->setSystemUiLightsOut(lightsOut); } static jboolean nativeTransferTouchFocus(JNIEnv* env, @@ -1802,7 +1801,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"nativeSetFocusedDisplay", "(JI)V", (void*)nativeSetFocusedDisplay}, {"nativeSetPointerCapture", "(JZ)V", (void*)nativeSetPointerCapture}, {"nativeSetInputDispatchMode", "(JZZ)V", (void*)nativeSetInputDispatchMode}, - {"nativeSetSystemUiVisibility", "(JI)V", (void*)nativeSetSystemUiVisibility}, + {"nativeSetSystemUiLightsOut", "(JZ)V", (void*)nativeSetSystemUiLightsOut}, {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)Z", (void*)nativeTransferTouchFocus}, {"nativeSetPointerSpeed", "(JI)V", (void*)nativeSetPointerSpeed}, diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 4e53aa218b71..91645ba1a9b9 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -30,9 +30,12 @@ #include <android/hardware/gnss/2.1/IGnssAntennaInfo.h> #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/BnGnssPsdsCallback.h> #include <android/hardware/gnss/measurement_corrections/1.0/IMeasurementCorrections.h> #include <android/hardware/gnss/measurement_corrections/1.1/IMeasurementCorrections.h> #include <android/hardware/gnss/visibility_control/1.0/IGnssVisibilityControl.h> +#include <binder/IServiceManager.h> #include <nativehelper/JNIHelp.h> #include "android_runtime/AndroidRuntime.h" #include "android_runtime/Log.h" @@ -142,9 +145,10 @@ static JavaVM* sJvm; using android::OK; using android::sp; -using android::wp; using android::status_t; using android::String16; +using android::wp; +using android::binder::Status; using android::hardware::Return; using android::hardware::Void; @@ -152,6 +156,7 @@ using android::hardware::hidl_vec; using android::hardware::hidl_string; using android::hardware::hidl_death_recipient; +using android::hardware::gnss::PsdsType; using android::hardware::gnss::V1_0::GnssLocationFlags; using android::hardware::gnss::V1_0::IAGnssRilCallback; using android::hardware::gnss::V1_0::IGnssGeofenceCallback; @@ -161,11 +166,10 @@ using android::hardware::gnss::V1_0::IGnssNavigationMessageCallback; using android::hardware::gnss::V1_0::IGnssNi; using android::hardware::gnss::V1_0::IGnssNiCallback; using android::hardware::gnss::V1_0::IGnssXtra; +using android::hardware::gnss::V2_0::ElapsedRealtimeFlags; using android::hardware::gnss::V3_0::IGnssPsds; using android::hardware::gnss::V3_0::IGnssPsdsCallback; -using android::hardware::gnss::V2_0::ElapsedRealtimeFlags; - using MeasurementCorrections_V1_0 = android::hardware::gnss::measurement_corrections::V1_0::MeasurementCorrections; using MeasurementCorrections_V1_1 = android::hardware::gnss::measurement_corrections::V1_1::MeasurementCorrections; @@ -224,6 +228,10 @@ using android::hardware::gnss::measurement_corrections::V1_0::GnssSingleSatCorre using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControl; using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControlCallback; +using IGnssAidl = android::hardware::gnss::IGnss; +using IGnssPsdsAidl = android::hardware::gnss::IGnssPsds; +using IGnssPsdsCallbackAidl = android::hardware::gnss::IGnssPsdsCallback; + struct GnssDeathRecipient : virtual public hidl_death_recipient { // hidl_death_recipient interface @@ -245,7 +253,9 @@ sp<IGnss_V1_1> gnssHal_V1_1 = nullptr; sp<IGnss_V2_0> gnssHal_V2_0 = nullptr; sp<IGnss_V2_1> gnssHal_V2_1 = nullptr; sp<IGnss_V3_0> gnssHal_V3_0 = nullptr; +sp<IGnssAidl> gnssHalAidl = nullptr; sp<IGnssPsds> gnssPsdsIface = nullptr; +sp<IGnssPsdsAidl> gnssPsdsAidlIface = nullptr; sp<IGnssXtra> gnssXtraIface = nullptr; sp<IAGnssRil_V1_0> agnssRilIface = nullptr; sp<IAGnssRil_V2_0> agnssRilIface_V2_0 = nullptr; @@ -445,6 +455,19 @@ static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodNa } } +static jboolean checkAidlStatus(const Status& status, const char* errorMessage, + const bool success) { + if (!status.isOk()) { + ALOGE("%s AIDL transport error: %s", errorMessage, status.toString8().c_str()); + return JNI_FALSE; + } + if (!success) { + ALOGE("AIDL return failure: %s", errorMessage); + return JNI_FALSE; + } + return JNI_TRUE; +} + static jobject createHalInterfaceVersionJavaObject(JNIEnv* env, jint major, jint minor) { jobject version = env->NewObject(class_gnssConfiguration_halInterfaceVersion, method_halInterfaceVersionCtor, major, minor); @@ -928,20 +951,31 @@ Return<void> GnssCallback::gnssSetSystemInfoCb(const IGnssCallback_V2_0::GnssSys * GnssPsdsCallback class implements the callback methods for the IGnssPsds * interface. */ +struct GnssPsdsCallbackAidl : public android::hardware::gnss::BnGnssPsdsCallback { + Status downloadRequestCb(PsdsType psdsType) override { + ALOGD("%s. psdsType: %d", __func__, static_cast<int32_t>(psdsType)); + JNIEnv* env = getJniEnv(); + env->CallVoidMethod(mCallbacksObj, method_psdsDownloadRequest, psdsType); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return Status::ok(); + } +}; + +/* + * GnssPsdsCallback class implements the callback methods for the IGnssPsds + * interface. + */ struct GnssPsdsCallback : public IGnssPsdsCallback { Return<void> downloadRequestCb() override; Return<void> downloadRequestCb_3_0(int32_t psdsType) override; }; Return<void> GnssPsdsCallback::downloadRequestCb() { - JNIEnv* env = getJniEnv(); - env->CallVoidMethod(mCallbacksObj, method_psdsDownloadRequest, /* psdsType= */ 1); - checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); + return downloadRequestCb_3_0(/* psdsType= */ 1); } Return<void> GnssPsdsCallback::downloadRequestCb_3_0(int32_t psdsType) { - ALOGD("%s: %d", __func__, psdsType); + ALOGD("%s. psdsType: %d", __func__, psdsType); JNIEnv* env = getJniEnv(); env->CallVoidMethod(mCallbacksObj, method_psdsDownloadRequest, psdsType); checkAndClearExceptionFromCallback(env, __FUNCTION__); @@ -1917,6 +1951,11 @@ struct GnssBatchingCallback_V2_0 : public IGnssBatchingCallback_V2_0 { /* Initializes the GNSS service handle. */ static void android_location_GnssLocationProvider_set_gps_service_handle() { + gnssHalAidl = waitForVintfService<IGnssAidl>(); + if (gnssHalAidl != nullptr) { + ALOGD("Successfully got GNSS AIDL handle."); + } + ALOGD("Trying IGnss_V3_0::getService()"); gnssHal_V3_0 = IGnss_V3_0::getService(); if (gnssHal_V3_0 != nullptr) { @@ -2168,7 +2207,15 @@ static void android_location_GnssNative_init_once(JNIEnv* env, jobject obj, ALOGD("Link to death notification successful"); } - if (gnssHal_V3_0 != nullptr) { + if (gnssHalAidl != nullptr) { + sp<IGnssPsdsAidl> gnssPsdsAidl; + auto status = gnssHalAidl->getExtensionPsds(&gnssPsdsAidl); + if (status.isOk()) { + gnssPsdsAidlIface = gnssPsdsAidl; + } else { + ALOGD("Unable to get a handle to PSDS AIDL interface."); + } + } else if (gnssHal_V3_0 != nullptr) { auto gnssPsds = gnssHal_V3_0->getExtensionPsds(); if (!gnssPsds.isOk()) { ALOGD("Unable to get a handle to Psds"); @@ -2470,19 +2517,28 @@ static jboolean android_location_GnssLocationProvider_init(JNIEnv* /* env */, jo } // Set IGnssPsds or IGnssXtra callback. - sp<IGnssPsdsCallback> gnssPsdsCbIface = new GnssPsdsCallback(); - if (gnssPsdsIface != nullptr) { - result = gnssPsdsIface->setCallback_3_0(gnssPsdsCbIface); - if (!checkHidlReturn(result, "IGnssPsds setCallback() failed.")) { - gnssPsdsIface = nullptr; - } - } else if (gnssXtraIface != nullptr) { - result = gnssXtraIface->setCallback(gnssPsdsCbIface); - if (!checkHidlReturn(result, "IGnssXtra setCallback() failed.")) { - gnssXtraIface = nullptr; + if (gnssPsdsAidlIface != nullptr) { + sp<IGnssPsdsCallbackAidl> gnssPsdsCallbackAidl = new GnssPsdsCallbackAidl(); + bool success; + auto status = gnssPsdsAidlIface->setCallback(gnssPsdsCallbackAidl, &success); + if (!checkAidlStatus(status, "IGnssPsdsAidl setCallback() failed.", success)) { + gnssPsdsAidlIface = nullptr; } } else { - ALOGI("Unable to initialize IGnssPsds/IGnssXtra interface."); + sp<IGnssPsdsCallback> gnssPsdsCbIface = new GnssPsdsCallback(); + if (gnssPsdsIface != nullptr) { + result = gnssPsdsIface->setCallback_3_0(gnssPsdsCbIface); + if (!checkHidlReturn(result, "IGnssPsds setCallback() failed.")) { + gnssPsdsIface = nullptr; + } + } else if (gnssXtraIface != nullptr) { + result = gnssXtraIface->setCallback(gnssPsdsCbIface); + if (!checkHidlReturn(result, "IGnssXtra setCallback() failed.")) { + gnssXtraIface = nullptr; + } + } else { + ALOGI("Unable to initialize IGnssPsds/IGnssXtra interface."); + } } // Set IAGnss.hal callback. @@ -2743,19 +2799,29 @@ static void android_location_GnssLocationProvider_inject_location(JNIEnv* /* env static jboolean android_location_GnssLocationProvider_supports_psds( JNIEnv* /* env */, jobject /* obj */) { - return (gnssPsdsIface != nullptr || gnssXtraIface != nullptr) ? JNI_TRUE : JNI_FALSE; + return (gnssPsdsAidlIface != nullptr || gnssPsdsIface != nullptr || gnssXtraIface != nullptr) + ? JNI_TRUE + : JNI_FALSE; } static void android_location_GnssLocationProvider_inject_psds_data(JNIEnv* env, jobject /* obj */, jbyteArray data, jint length, jint psdsType) { - if (gnssPsdsIface == nullptr && gnssXtraIface == nullptr) { + if (gnssPsdsAidlIface == nullptr && gnssPsdsIface == nullptr && gnssXtraIface == nullptr) { ALOGE("%s: IGnssPsds or IGnssXtra interface not available.", __func__); return; } jbyte* bytes = reinterpret_cast<jbyte *>(env->GetPrimitiveArrayCritical(data, 0)); - if (gnssPsdsIface != nullptr) { + if (gnssPsdsAidlIface != nullptr) { + bool success; + auto status = gnssPsdsAidlIface->injectPsdsData(static_cast<PsdsType>(psdsType), + std::vector<uint8_t>((const uint8_t*)bytes, + (const uint8_t*)bytes + + length), + &success); + checkAidlStatus(status, "IGnssPsdsAidl injectPsdsData() failed.", success); + } else if (gnssPsdsIface != nullptr) { auto result = gnssPsdsIface->injectPsdsData_3_0(psdsType, std::string((const char*)bytes, length)); checkHidlReturn(result, "IGnssPsds injectPsdsData() failed."); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 8f1fb127ec17..f348664213cd 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -2266,10 +2266,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new SecurityException("Admin " + admin.info.getComponent() + " does not own the profile"); } - if (reqPolicy == DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER) { - throw new SecurityException("Admin " + admin.info.getComponent() - + " is not the profile owner on organization-owned device"); - } if (DA_DISALLOWED_POLICIES.contains(reqPolicy) && !isDeviceOwner && !isProfileOwner) { throw new SecurityException("Admin " + admin.info.getComponent() + " is not a device owner or profile owner, so may not use policy: " @@ -2381,8 +2377,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) { return ownsDevice; - } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER) { - return ownsDevice || ownsProfileOnOrganizationOwnedDevice; } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) { // DO always has the PO power. return ownsDevice || ownsProfileOnOrganizationOwnedDevice || ownsProfile; @@ -5945,14 +5939,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Preconditions.checkNotNull(who, "ComponentName is null"); + CallerIdentity caller = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow(); - final int userId = mInjector.userHandleGetCallingUserId(); synchronized (getLockObject()) { - ActiveAdmin admin = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER); + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); admin.mFactoryResetProtectionPolicy = policy; - saveSettingsLocked(userId); + saveSettingsLocked(caller.getUserId()); } final Intent intent = new Intent( @@ -5977,21 +5972,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow(); - ActiveAdmin admin; + final ActiveAdmin admin; synchronized (getLockObject()) { if (who == null) { - if ((frpManagementAgentUid != mInjector.binderGetCallingUid()) - && !hasCallingPermission(permission.MASTER_CLEAR)) { - throw new SecurityException( - "Must be called by the FRP management agent on device"); - } + Preconditions.checkCallAuthorization( + frpManagementAgentUid == mInjector.binderGetCallingUid() + || hasCallingPermission(permission.MASTER_CLEAR), + "Must be called by the FRP management agent on device"); admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( UserHandle.getUserId(frpManagementAgentUid)); } else { - admin = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER); + final CallerIdentity caller = getCallerIdentity(who); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)); + admin = getProfileOwnerOrDeviceOwnerLocked(caller); } } + return admin != null ? admin.mFactoryResetProtectionPolicy : null; } @@ -9715,8 +9712,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { boolean result; synchronized (getLockObject()) { if (parent) { - getActiveAdminForCallerLocked(who, - DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, parent); + Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice( + caller.getUserId()) && isManagedProfile(caller.getUserId())); // Ensure the package provided is a system package, this is to ensure that this // API cannot be used to leak if certain non-system package exists in the person // profile. @@ -9747,8 +9744,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId(); synchronized (getLockObject()) { if (parent) { - getActiveAdminForCallerLocked(who, - DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, parent); + Preconditions.checkCallAuthorization( + isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId()) + && isManagedProfile(caller.getUserId())); // Ensure the package provided is a system package. mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(packageName, userId)); @@ -11457,9 +11455,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { policy.validateAgainstPreviousFreezePeriod(record.first, record.second, LocalDate.now()); } + final CallerIdentity caller = getCallerIdentity(who); + synchronized (getLockObject()) { - getActiveAdminForCallerLocked(who, - DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER); + Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller) + || isDeviceOwner(caller)); + if (policy == null) { mOwners.clearSystemUpdatePolicy(); } else { @@ -12667,10 +12668,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Objects.requireNonNull(admin); + final CallerIdentity caller = getCallerIdentity(admin); synchronized (getLockObject()) { - getActiveAdminForCallerLocked(admin, - DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER); + Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller) + || isDeviceOwner(caller)); if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) { return; } @@ -12698,8 +12700,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { if (!isCallerWithSystemUid()) { Objects.requireNonNull(admin); - getActiveAdminForCallerLocked(admin, - DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER); + final CallerIdentity caller = getCallerIdentity(admin); + Preconditions.checkCallAuthorization( + isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller)); } return mInjector.securityLogGetLoggingEnabledProperty(); } @@ -14576,12 +14579,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setCommonCriteriaModeEnabled(ComponentName who, boolean enabled) { - final int userId = mInjector.userHandleGetCallingUserId(); + Objects.requireNonNull(who, "Admin component name must be provided"); + final CallerIdentity caller = getCallerIdentity(who); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "Common Criteria mode can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); synchronized (getLockObject()) { - final ActiveAdmin admin = getActiveAdminForCallerLocked(who, - DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER); + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); admin.mCommonCriteriaMode = enabled; - saveSettingsLocked(userId); + saveSettingsLocked(caller.getUserId()); } DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_COMMON_CRITERIA_MODE) @@ -14593,9 +14600,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean isCommonCriteriaModeEnabled(ComponentName who) { if (who != null) { + final CallerIdentity caller = getCallerIdentity(who); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "Common Criteria mode can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); + synchronized (getLockObject()) { - final ActiveAdmin admin = getActiveAdminForCallerLocked(who, - DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER); + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); return admin.mCommonCriteriaMode; } } @@ -14618,9 +14630,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)); synchronized (getLockObject()) { - final ActiveAdmin admin = getActiveAdminForCallerLocked(who, - DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, - false /* parent */); + final ActiveAdmin admin = getProfileOwnerOfCallerLocked(caller); final long deadline = admin.mProfileOffDeadline; final int result = makeSuspensionReasons(admin.mSuspendPersonalApps, deadline != 0 && mInjector.systemCurrentTimeMillis() > deadline); @@ -14653,9 +14663,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int callingUserId = caller.getUserId(); synchronized (getLockObject()) { - final ActiveAdmin admin = getActiveAdminForCallerLocked(who, - DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, - false /* parent */); + final ActiveAdmin admin = getProfileOwnerOfCallerLocked(caller); boolean shouldSaveSettings = false; if (admin.mSuspendPersonalApps != suspended) { admin.mSuspendPersonalApps = suspended; @@ -14916,9 +14924,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int userId = caller.getUserId(); synchronized (getLockObject()) { - final ActiveAdmin admin = getActiveAdminForCallerLocked(who, - DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, - false /* parent */); + final ActiveAdmin admin = getProfileOwnerOfCallerLocked(caller); // Ensure the timeout is long enough to avoid having bad user experience. if (timeoutMillis > 0 && timeoutMillis < MANAGED_PROFILE_MAXIMUM_TIME_OFF_THRESHOLD @@ -14963,9 +14969,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)); synchronized (getLockObject()) { - final ActiveAdmin admin = getActiveAdminForCallerLocked(who, - DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, - false /* parent */); + final ActiveAdmin admin = getProfileOwnerOfCallerLocked(caller); return admin.mProfileMaximumTimeOffMillis; } } diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index e8bf468f032e..bbcb3122c9bb 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -1468,7 +1468,7 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ // Need a shared pointer: will be passing it into all unpacking jobs. std::shared_ptr<ZipArchive> zipFile(zipFileHandle, [](ZipArchiveHandle h) { CloseArchive(h); }); void* cookie = nullptr; - const auto libFilePrefix = path::join(constants().libDir, abi); + const auto libFilePrefix = path::join(constants().libDir, abi) + "/"; if (StartIteration(zipFile.get(), &cookie, libFilePrefix, constants().libSuffix)) { LOG(ERROR) << "Failed to start zip iteration for " << apkFullPath; return false; diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java index 33317a38853e..8b1e9c52ec58 100644 --- a/services/people/java/com/android/server/people/PeopleService.java +++ b/services/people/java/com/android/server/people/PeopleService.java @@ -40,7 +40,6 @@ import com.android.internal.annotations.VisibleForTesting; 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; @@ -95,28 +94,57 @@ public class PeopleService extends SystemService { * @throws SecurityException if the caller is not system or root */ private static void enforceSystemOrRoot(String message) { - int uid = Binder.getCallingUid(); - if (!UserHandle.isSameApp(uid, Process.SYSTEM_UID) && uid != Process.ROOT_UID) { + if (!isSystemOrRoot()) { throw new SecurityException("Only system may " + message); } } + private static boolean isSystemOrRoot() { + final int uid = Binder.getCallingUid(); + return UserHandle.isSameApp(uid, Process.SYSTEM_UID) || uid == Process.ROOT_UID; + } + + + /** + * Enforces that only the system, root UID or SystemUI can make certain calls. + * + * @param message used as message if SecurityException is thrown + * @throws SecurityException if the caller is not system or root + */ + private static void enforceSystemRootOrSystemUI(Context context, String message) { + if (isSystemOrRoot()) return; + context.enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE, + message); + } + private final class BinderService extends IPeopleManager.Stub { @Override public ParceledListSlice<ConversationChannel> getRecentConversations() { enforceSystemOrRoot("get recent conversations"); - return new ParceledListSlice<>(new ArrayList<>()); + return new ParceledListSlice<>( + mDataManager.getRecentConversations( + Binder.getCallingUserHandle().getIdentifier())); } @Override public void removeRecentConversation(String packageName, int userId, String shortcutId) { enforceSystemOrRoot("remove a recent conversation"); + mDataManager.removeRecentConversation(packageName, userId, shortcutId, + Binder.getCallingUserHandle().getIdentifier()); } @Override public void removeAllRecentConversations() { enforceSystemOrRoot("remove all recent conversations"); + mDataManager.removeAllRecentConversations( + Binder.getCallingUserHandle().getIdentifier()); + } + + @Override + public long getLastInteraction(String packageName, int userId, String shortcutId) { + enforceSystemRootOrSystemUI(getContext(), "get last interaction"); + return mDataManager.getLastInteraction(packageName, userId, shortcutId); } } 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 17378285276f..45f389cbd3ff 100644 --- a/services/people/java/com/android/server/people/data/ConversationInfo.java +++ b/services/people/java/com/android/server/people/data/ConversationInfo.java @@ -90,6 +90,11 @@ public class ConversationInfo { @Nullable private String mNotificationChannelId; + @Nullable + private String mParentNotificationChannelId; + + private long mLastEventTimestamp; + @ShortcutFlags private int mShortcutFlags; @@ -102,6 +107,8 @@ public class ConversationInfo { mContactUri = builder.mContactUri; mContactPhoneNumber = builder.mContactPhoneNumber; mNotificationChannelId = builder.mNotificationChannelId; + mParentNotificationChannelId = builder.mParentNotificationChannelId; + mLastEventTimestamp = builder.mLastEventTimestamp; mShortcutFlags = builder.mShortcutFlags; mConversationFlags = builder.mConversationFlags; } @@ -129,14 +136,32 @@ public class ConversationInfo { } /** - * ID of the {@link android.app.NotificationChannel} where the notifications for this - * conversation are posted. + * ID of the conversation-specific {@link android.app.NotificationChannel} where the + * notifications for this conversation are posted. */ @Nullable String getNotificationChannelId() { return mNotificationChannelId; } + /** + * ID of the parent {@link android.app.NotificationChannel} for this conversation. This is the + * notification channel where the notifications are posted before this conversation is + * customized by the user. + */ + @Nullable + String getParentNotificationChannelId() { + return mParentNotificationChannelId; + } + + /** + * Timestamp of the last event, {@code 0L} if there are no events. This timestamp is for + * identifying and sorting the recent conversations. It may only count a subset of event types. + */ + long getLastEventTimestamp() { + return mLastEventTimestamp; + } + /** Whether the shortcut for this conversation is set long-lived by the app. */ public boolean isShortcutLongLived() { return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED); @@ -202,6 +227,8 @@ public class ConversationInfo { && Objects.equals(mContactUri, other.mContactUri) && Objects.equals(mContactPhoneNumber, other.mContactPhoneNumber) && Objects.equals(mNotificationChannelId, other.mNotificationChannelId) + && Objects.equals(mParentNotificationChannelId, other.mParentNotificationChannelId) + && Objects.equals(mLastEventTimestamp, other.mLastEventTimestamp) && mShortcutFlags == other.mShortcutFlags && mConversationFlags == other.mConversationFlags; } @@ -209,7 +236,8 @@ public class ConversationInfo { @Override public int hashCode() { return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber, - mNotificationChannelId, mShortcutFlags, mConversationFlags); + mNotificationChannelId, mParentNotificationChannelId, mLastEventTimestamp, + mShortcutFlags, mConversationFlags); } @Override @@ -221,6 +249,8 @@ public class ConversationInfo { sb.append(", contactUri=").append(mContactUri); sb.append(", phoneNumber=").append(mContactPhoneNumber); sb.append(", notificationChannelId=").append(mNotificationChannelId); + sb.append(", parentNotificationChannelId=").append(mParentNotificationChannelId); + sb.append(", lastEventTimestamp=").append(mLastEventTimestamp); sb.append(", shortcutFlags=0x").append(Integer.toHexString(mShortcutFlags)); sb.append(" ["); if (isShortcutLongLived()) { @@ -280,6 +310,11 @@ public class ConversationInfo { protoOutputStream.write(ConversationInfoProto.NOTIFICATION_CHANNEL_ID, mNotificationChannelId); } + if (mParentNotificationChannelId != null) { + protoOutputStream.write(ConversationInfoProto.PARENT_NOTIFICATION_CHANNEL_ID, + mParentNotificationChannelId); + } + protoOutputStream.write(ConversationInfoProto.LAST_EVENT_TIMESTAMP, mLastEventTimestamp); protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags); protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags); if (mContactPhoneNumber != null) { @@ -300,6 +335,8 @@ public class ConversationInfo { out.writeInt(mShortcutFlags); out.writeInt(mConversationFlags); out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : ""); + out.writeUTF(mParentNotificationChannelId != null ? mParentNotificationChannelId : ""); + out.writeLong(mLastEventTimestamp); } catch (IOException e) { Slog.e(TAG, "Failed to write fields to backup payload.", e); return null; @@ -338,6 +375,14 @@ public class ConversationInfo { builder.setNotificationChannelId(protoInputStream.readString( ConversationInfoProto.NOTIFICATION_CHANNEL_ID)); break; + case (int) ConversationInfoProto.PARENT_NOTIFICATION_CHANNEL_ID: + builder.setParentNotificationChannelId(protoInputStream.readString( + ConversationInfoProto.PARENT_NOTIFICATION_CHANNEL_ID)); + break; + case (int) ConversationInfoProto.LAST_EVENT_TIMESTAMP: + builder.setLastEventTimestamp(protoInputStream.readLong( + ConversationInfoProto.LAST_EVENT_TIMESTAMP)); + break; case (int) ConversationInfoProto.SHORTCUT_FLAGS: builder.setShortcutFlags(protoInputStream.readInt( ConversationInfoProto.SHORTCUT_FLAGS)); @@ -382,6 +427,11 @@ public class ConversationInfo { if (!TextUtils.isEmpty(contactPhoneNumber)) { builder.setContactPhoneNumber(contactPhoneNumber); } + String parentNotificationChannelId = in.readUTF(); + if (!TextUtils.isEmpty(parentNotificationChannelId)) { + builder.setParentNotificationChannelId(parentNotificationChannelId); + } + builder.setLastEventTimestamp(in.readLong()); } catch (IOException e) { Slog.e(TAG, "Failed to read conversation info fields from backup payload.", e); return null; @@ -408,6 +458,11 @@ public class ConversationInfo { @Nullable private String mNotificationChannelId; + @Nullable + private String mParentNotificationChannelId; + + private long mLastEventTimestamp; + @ShortcutFlags private int mShortcutFlags; @@ -427,6 +482,8 @@ public class ConversationInfo { mContactUri = conversationInfo.mContactUri; mContactPhoneNumber = conversationInfo.mContactPhoneNumber; mNotificationChannelId = conversationInfo.mNotificationChannelId; + mParentNotificationChannelId = conversationInfo.mParentNotificationChannelId; + mLastEventTimestamp = conversationInfo.mLastEventTimestamp; mShortcutFlags = conversationInfo.mShortcutFlags; mConversationFlags = conversationInfo.mConversationFlags; } @@ -456,6 +513,16 @@ public class ConversationInfo { return this; } + Builder setParentNotificationChannelId(String parentNotificationChannelId) { + mParentNotificationChannelId = parentNotificationChannelId; + return this; + } + + Builder setLastEventTimestamp(long lastEventTimestamp) { + mLastEventTimestamp = lastEventTimestamp; + return this; + } + Builder setShortcutFlags(@ShortcutFlags int shortcutFlags) { mShortcutFlags = shortcutFlags; return 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 4d1129567ca6..87f2c581ef8f 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -24,6 +24,7 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Person; +import android.app.people.ConversationChannel; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.usage.UsageEvents; @@ -74,8 +75,11 @@ import com.android.server.notification.ShortcutHelper; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.PriorityQueue; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -96,6 +100,7 @@ public class DataManager { private static final long QUERY_EVENTS_MAX_AGE_MS = 5L * DateUtils.MINUTE_IN_MILLIS; private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L; + @VisibleForTesting static final int MAX_CACHED_RECENT_SHORTCUTS = 30; private final Context mContext; private final Injector mInjector; @@ -208,6 +213,83 @@ public class DataManager { mContext.getPackageName(), intentFilter, callingUserId); } + /** Returns the cached non-customized recent conversations. */ + public List<ConversationChannel> getRecentConversations(@UserIdInt int callingUserId) { + List<ConversationChannel> conversationChannels = new ArrayList<>(); + forPackagesInProfile(callingUserId, packageData -> { + String packageName = packageData.getPackageName(); + int userId = packageData.getUserId(); + packageData.forAllConversations(conversationInfo -> { + if (!isCachedRecentConversation(conversationInfo)) { + return; + } + String shortcutId = conversationInfo.getShortcutId(); + ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId); + int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId); + NotificationChannel parentChannel = + mNotificationManagerInternal.getNotificationChannel(packageName, uid, + conversationInfo.getParentNotificationChannelId()); + if (shortcutInfo == null || parentChannel == null) { + return; + } + conversationChannels.add( + new ConversationChannel(shortcutInfo, parentChannel, + conversationInfo.getLastEventTimestamp(), + hasActiveNotifications(packageName, userId, shortcutId))); + }); + }); + return conversationChannels; + } + + /** + * Uncaches the shortcut that's associated with the specified conversation so this conversation + * will not show up in the recent conversations list. + */ + public void removeRecentConversation(String packageName, int userId, String shortcutId, + @UserIdInt int callingUserId) { + if (!hasActiveNotifications(packageName, userId, shortcutId)) { + mShortcutServiceInternal.uncacheShortcuts(callingUserId, mContext.getPackageName(), + packageName, Collections.singletonList(shortcutId), userId, + ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); + } + } + + /** + * Uncaches the shortcuts for all the recent conversations that they don't have active + * notifications. + */ + public void removeAllRecentConversations(@UserIdInt int callingUserId) { + forPackagesInProfile(callingUserId, packageData -> { + String packageName = packageData.getPackageName(); + int userId = packageData.getUserId(); + List<String> idsToUncache = new ArrayList<>(); + packageData.forAllConversations(conversationInfo -> { + String shortcutId = conversationInfo.getShortcutId(); + if (isCachedRecentConversation(conversationInfo) + && !hasActiveNotifications(packageName, userId, shortcutId)) { + idsToUncache.add(shortcutId); + } + }); + mShortcutServiceInternal.uncacheShortcuts(callingUserId, mContext.getPackageName(), + packageName, idsToUncache, userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); + }); + } + + /** + * Returns the last notification interaction with the specified conversation. If the + * conversation can't be found or no interactions have been recorded, returns 0L. + */ + public long getLastInteraction(String packageName, int userId, String shortcutId) { + final PackageData packageData = getPackage(packageName, userId); + if (packageData != null) { + final ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId); + if (conversationInfo != null) { + return conversationInfo.getLastEventTimestamp(); + } + } + return 0L; + } + /** Reports the sharing related {@link AppTargetEvent} from App Prediction Manager. */ public void reportShareTargetEvent(@NonNull AppTargetEvent event, @NonNull IntentFilter intentFilter) { @@ -277,7 +359,6 @@ public class DataManager { } pruneUninstalledPackageData(userData); - final NotificationListener notificationListener = mNotificationListeners.get(userId); userData.forAllPackages(packageData -> { if (signal.isCanceled()) { return; @@ -290,20 +371,7 @@ public class DataManager { packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS); } packageData.pruneOrphanEvents(); - if (notificationListener != null) { - String packageName = packageData.getPackageName(); - packageData.forAllConversations(conversationInfo -> { - if (conversationInfo.isShortcutCachedForNotification() - && conversationInfo.getNotificationChannelId() == null - && !notificationListener.hasActiveNotifications( - packageName, conversationInfo.getShortcutId())) { - mShortcutServiceInternal.uncacheShortcuts(userId, - mContext.getPackageName(), packageName, - Collections.singletonList(conversationInfo.getShortcutId()), - userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); - } - }); - } + cleanupCachedShortcuts(userId, MAX_CACHED_RECENT_SHORTCUTS); }); } @@ -466,7 +534,8 @@ public class DataManager { @NonNull String packageName, @UserIdInt int userId, @Nullable List<String> shortcutIds) { @ShortcutQuery.QueryFlags int queryFlags = ShortcutQuery.FLAG_MATCH_DYNAMIC - | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER; + | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER + | ShortcutQuery.FLAG_MATCH_CACHED; return mShortcutServiceInternal.getShortcuts( UserHandle.USER_SYSTEM, mContext.getPackageName(), /*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null, @@ -526,6 +595,68 @@ public class DataManager { return packageData; } + private boolean isCachedRecentConversation(ConversationInfo conversationInfo) { + return conversationInfo.isShortcutCachedForNotification() + && conversationInfo.getNotificationChannelId() == null + && conversationInfo.getParentNotificationChannelId() != null + && conversationInfo.getLastEventTimestamp() > 0L; + } + + private boolean hasActiveNotifications(String packageName, @UserIdInt int userId, + String shortcutId) { + NotificationListener notificationListener = mNotificationListeners.get(userId); + return notificationListener != null + && notificationListener.hasActiveNotifications(packageName, shortcutId); + } + + /** + * Cleans up the oldest cached shortcuts that don't have active notifications for the recent + * conversations. After the cleanup, normally, the total number of cached shortcuts will be + * less than or equal to the target count. However, there are exception cases: e.g. when all + * the existing cached shortcuts have active notifications. + */ + private void cleanupCachedShortcuts(@UserIdInt int userId, int targetCachedCount) { + UserData userData = getUnlockedUserData(userId); + if (userData == null) { + return; + } + // pair of <package name, conversation info> + List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>(); + userData.forAllPackages(packageData -> + packageData.forAllConversations(conversationInfo -> { + if (isCachedRecentConversation(conversationInfo)) { + cachedConvos.add( + Pair.create(packageData.getPackageName(), conversationInfo)); + } + }) + ); + if (cachedConvos.size() <= targetCachedCount) { + return; + } + int numToUncache = cachedConvos.size() - targetCachedCount; + // Max heap keeps the oldest cached conversations. + PriorityQueue<Pair<String, ConversationInfo>> maxHeap = new PriorityQueue<>( + numToUncache + 1, + Comparator.comparingLong((Pair<String, ConversationInfo> pair) -> + pair.second.getLastEventTimestamp()).reversed()); + for (Pair<String, ConversationInfo> cached : cachedConvos) { + if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) { + continue; + } + maxHeap.offer(cached); + if (maxHeap.size() > numToUncache) { + maxHeap.poll(); + } + } + while (!maxHeap.isEmpty()) { + Pair<String, ConversationInfo> toUncache = maxHeap.poll(); + mShortcutServiceInternal.uncacheShortcuts(userId, + mContext.getPackageName(), toUncache.first, + Collections.singletonList(toUncache.second.getShortcutId()), + userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); + } + } + @VisibleForTesting @WorkerThread void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) { @@ -736,9 +867,21 @@ public class DataManager { public void onShortcutsAddedOrUpdated(@NonNull String packageName, @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) { mInjector.getBackgroundExecutor().execute(() -> { + PackageData packageData = getPackage(packageName, user.getIdentifier()); for (ShortcutInfo shortcut : shortcuts) { if (ShortcutHelper.isConversationShortcut( shortcut, mShortcutServiceInternal, user.getIdentifier())) { + if (shortcut.isCached()) { + ConversationInfo conversationInfo = packageData != null + ? packageData.getConversationInfo(shortcut.getId()) : null; + if (conversationInfo == null + || !conversationInfo.isShortcutCachedForNotification()) { + // This is a newly cached shortcut. Clean up the existing cached + // shortcuts to ensure the cache size is under the limit. + cleanupCachedShortcuts(user.getIdentifier(), + MAX_CACHED_RECENT_SHORTCUTS - 1); + } + } addOrUpdateConversationInfo(shortcut); } } @@ -757,14 +900,16 @@ public class DataManager { Slog.e(TAG, "Package not found: " + packageName, e); } PackageData packageData = getPackage(packageName, user.getIdentifier()); + Set<String> shortcutIds = new HashSet<>(); for (ShortcutInfo shortcutInfo : shortcuts) { if (packageData != null) { packageData.deleteDataForConversation(shortcutInfo.getId()); } - if (uid != Process.INVALID_UID) { - mNotificationManagerInternal.onConversationRemoved( - shortcutInfo.getPackage(), uid, shortcutInfo.getId()); - } + shortcutIds.add(shortcutInfo.getId()); + } + if (uid != Process.INVALID_UID) { + mNotificationManagerInternal.onConversationRemoved( + packageName, uid, shortcutIds); } }); } @@ -797,6 +942,16 @@ public class DataManager { }); if (packageData != null) { + ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId); + if (conversationInfo == null) { + return; + } + ConversationInfo updated = new ConversationInfo.Builder(conversationInfo) + .setLastEventTimestamp(sbn.getPostTime()) + .setParentNotificationChannelId(sbn.getNotification().getChannelId()) + .build(); + packageData.getConversationStore().addOrUpdate(updated); + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( EventStore.CATEGORY_SHORTCUT_BASED, shortcutId); eventHistory.addEvent(new Event(sbn.getPostTime(), Event.TYPE_NOTIFICATION_POSTED)); @@ -817,16 +972,7 @@ public class DataManager { int count = mActiveNotifCounts.getOrDefault(conversationKey, 0) - 1; if (count <= 0) { mActiveNotifCounts.remove(conversationKey); - // The shortcut was cached by Notification Manager synchronously when the - // associated notification was posted. Uncache it here when all the - // associated notifications are removed. - if (conversationInfo.isShortcutCachedForNotification() - && conversationInfo.getNotificationChannelId() == null) { - mShortcutServiceInternal.uncacheShortcuts(mUserId, - mContext.getPackageName(), sbn.getPackageName(), - Collections.singletonList(conversationInfo.getShortcutId()), - mUserId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); - } + cleanupCachedShortcuts(mUserId, MAX_CACHED_RECENT_SHORTCUTS); } else { mActiveNotifCounts.put(conversationKey, count); } @@ -882,24 +1028,6 @@ public class DataManager { conversationStore.addOrUpdate(builder.build()); } - synchronized void cleanupCachedShortcuts() { - for (Pair<String, String> conversationKey : mActiveNotifCounts.keySet()) { - String packageName = conversationKey.first; - String shortcutId = conversationKey.second; - PackageData packageData = getPackage(packageName, mUserId); - ConversationInfo conversationInfo = - packageData != null ? packageData.getConversationInfo(shortcutId) : null; - if (conversationInfo != null - && conversationInfo.isShortcutCachedForNotification() - && conversationInfo.getNotificationChannelId() == null) { - mShortcutServiceInternal.uncacheShortcuts(mUserId, - mContext.getPackageName(), packageName, - Collections.singletonList(shortcutId), - mUserId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); - } - } - } - synchronized boolean hasActiveNotifications(String packageName, String shortcutId) { return mActiveNotifCounts.containsKey(Pair.create(packageName, shortcutId)); } @@ -972,16 +1100,7 @@ public class DataManager { @Override public void onReceive(Context context, Intent intent) { - forAllUnlockedUsers(userData -> { - NotificationListener listener = mNotificationListeners.get(userData.getUserId()); - // Clean up the cached shortcuts because all the notifications are cleared after - // system shutdown. The associated shortcuts need to be uncached to keep in sync - // unless the settings are changed by the user. - if (listener != null) { - listener.cleanupCachedShortcuts(); - } - userData.forAllPackages(PackageData::saveToDisk); - }); + forAllUnlockedUsers(userData -> userData.forAllPackages(PackageData::saveToDisk)); } } diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml index a398961db4c3..182fe9ae9476 100644 --- a/services/tests/mockingservicestests/AndroidManifest.xml +++ b/services/tests/mockingservicestests/AndroidManifest.xml @@ -17,7 +17,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.frameworks.mockingservicestests"> - <uses-sdk android:targetSdkVersion="30" /> + <uses-sdk android:targetSdkVersion="31" /> <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 5d8f662301c1..a250c217614d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -351,7 +351,7 @@ public class MockingOomAdjusterTests { doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController(); WindowProcessController wpc = app.getWindowProcessController(); doReturn(true).when(wpc).hasActivities(); - doAnswer(answer((minTaskLayer, callback) -> { + doAnswer(answer(callback -> { Field field = callback.getClass().getDeclaredField("adj"); field.set(callback, VISIBLE_APP_ADJ); field = callback.getClass().getDeclaredField("foregroundActivities"); @@ -361,7 +361,7 @@ public class MockingOomAdjusterTests { field = callback.getClass().getDeclaredField("schedGroup"); field.set(callback, SCHED_GROUP_TOP_APP); return 0; - })).when(wpc).computeOomAdjFromActivities(anyInt(), + })).when(wpc).computeOomAdjFromActivities( any(WindowProcessController.ComputeOomAdjCallback.class)); sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE; sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java index b4e8825c3902..be258dc5963e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java @@ -16,8 +16,6 @@ package com.android.server.location; -import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; -import static android.app.AlarmManager.WINDOW_EXACT; 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; @@ -41,7 +39,6 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.after; @@ -55,8 +52,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.MockitoAnnotations.initMocks; -import android.app.AlarmManager; -import android.app.AlarmManager.OnAlarmListener; import android.content.Context; import android.location.ILocationCallback; import android.location.ILocationListener; @@ -66,14 +61,11 @@ import android.location.LocationManagerInternal.ProviderEnabledListener; import android.location.LocationRequest; import android.location.util.identity.CallerIdentity; import android.os.Bundle; -import android.os.Handler; import android.os.ICancellationSignal; import android.os.IRemoteCallback; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; -import android.os.SystemClock; -import android.os.WorkSource; import android.platform.test.annotations.Presubmit; import android.util.Log; @@ -127,8 +119,6 @@ public class LocationProviderManagerTest { @Mock private Context mContext; @Mock - private AlarmManager mAlarmManager; - @Mock private PowerManager mPowerManager; @Mock private PowerManager.WakeLock mWakeLock; @@ -151,7 +141,6 @@ public class LocationProviderManagerTest { LocalServices.addService(LocationManagerInternal.class, mInternal); doReturn("android").when(mContext).getPackageName(); - doReturn(mAlarmManager).when(mContext).getSystemService(AlarmManager.class); doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class); doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString()); @@ -505,19 +494,8 @@ public class LocationProviderManagerTest { ILocationListener listener = createMockLocationListener(); LocationRequest request = new LocationRequest.Builder(0).setDurationMillis(5000).build(); mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener); - long baseTimeMs = SystemClock.elapsedRealtime(); - - ArgumentCaptor<Long> timeoutCapture = ArgumentCaptor.forClass(Long.class); - ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass( - OnAlarmListener.class); - verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), timeoutCapture.capture(), - eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class), - any(WorkSource.class)); - - assertThat(timeoutCapture.getValue()).isAtLeast(baseTimeMs + 4000); - assertThat(timeoutCapture.getValue()).isAtMost(baseTimeMs + 5000); - listenerCapture.getValue().onAlarm(); + mInjector.getAlarmHelper().incrementAlarmTime(5000); mProvider.setProviderLocation(createLocation(NAME, mRandom)); verify(listener, never()).onLocationChanged(any(Location.class), nullable(IRemoteCallback.class)); @@ -684,13 +662,7 @@ public class LocationProviderManagerTest { LocationRequest locationRequest = new LocationRequest.Builder(0).build(); mManager.getCurrentLocation(locationRequest, IDENTITY, PERMISSION_FINE, listener); - ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass( - OnAlarmListener.class); - verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), anyLong(), - eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class), - any(WorkSource.class)); - listenerCapture.getValue().onAlarm(); - + mInjector.getAlarmHelper().incrementAlarmTime(60000); verify(listener, times(1)).onLocation(isNull()); } @@ -769,6 +741,40 @@ public class LocationProviderManagerTest { } @Test + public void testProviderRequest_DelayedRequest() throws Exception { + mProvider.setProviderLocation(createLocation(NAME, mRandom)); + + ILocationListener listener1 = createMockLocationListener(); + LocationRequest request1 = new LocationRequest.Builder(60000).build(); + mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1); + + verify(listener1).onLocationChanged(any(Location.class), nullable(IRemoteCallback.class)); + + assertThat(mProvider.getRequest().isActive()).isFalse(); + + mInjector.getAlarmHelper().incrementAlarmTime(60000); + assertThat(mProvider.getRequest().isActive()).isTrue(); + assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(60000); + } + + @Test + public void testProviderRequest_SpamRequesting() { + mProvider.setProviderLocation(createLocation(NAME, mRandom)); + + ILocationListener listener1 = createMockLocationListener(); + LocationRequest request1 = new LocationRequest.Builder(60000).build(); + + mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1); + assertThat(mProvider.getRequest().isActive()).isFalse(); + mManager.unregisterLocationRequest(listener1); + assertThat(mProvider.getRequest().isActive()).isFalse(); + mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1); + assertThat(mProvider.getRequest().isActive()).isFalse(); + mManager.unregisterLocationRequest(listener1); + assertThat(mProvider.getRequest().isActive()).isFalse(); + } + + @Test public void testProviderRequest_BackgroundThrottle() { ILocationListener listener1 = createMockLocationListener(); LocationRequest request1 = new LocationRequest.Builder(5).build(); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java new file mode 100644 index 000000000000..0e3e6ef02a18 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java @@ -0,0 +1,61 @@ +/* + * 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.util; + +import android.app.AlarmManager.OnAlarmListener; +import android.os.WorkSource; + +import java.util.ArrayList; +import java.util.Iterator; + +public class FakeAlarmHelper extends AlarmHelper { + + private static class Alarm { + public long delayMs; + public final OnAlarmListener listener; + + Alarm(long delayMs, OnAlarmListener listener) { + this.delayMs = delayMs; + this.listener = listener; + } + } + + private final ArrayList<Alarm> mAlarms = new ArrayList<>(); + + @Override + public void setDelayedAlarmInternal(long delayMs, OnAlarmListener listener, + WorkSource workSource) { + mAlarms.add(new Alarm(delayMs, listener)); + } + + @Override + public void cancel(OnAlarmListener listener) { + mAlarms.removeIf(alarm -> alarm.listener == listener); + } + + public void incrementAlarmTime(long incrementMs) { + Iterator<Alarm> it = mAlarms.iterator(); + while (it.hasNext()) { + Alarm alarm = it.next(); + alarm.delayMs -= incrementMs; + if (alarm.delayMs <= 0) { + it.remove(); + alarm.listener.onAlarm(); + } + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java index 1867be0b9f3b..69f7376a4309 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java @@ -21,6 +21,7 @@ import com.android.server.location.LocationRequestStatistics; public class TestInjector implements Injector { private final FakeUserInfoHelper mUserInfoHelper; + private final FakeAlarmHelper mAlarmHelper; private final FakeAppOpsHelper mAppOpsHelper; private final FakeLocationPermissionsHelper mLocationPermissionsHelper; private final FakeSettingsHelper mSettingsHelper; @@ -33,6 +34,7 @@ public class TestInjector implements Injector { public TestInjector() { mUserInfoHelper = new FakeUserInfoHelper(); + mAlarmHelper = new FakeAlarmHelper(); mAppOpsHelper = new FakeAppOpsHelper(); mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper); mSettingsHelper = new FakeSettingsHelper(); @@ -50,6 +52,11 @@ public class TestInjector implements Injector { } @Override + public FakeAlarmHelper getAlarmHelper() { + return mAlarmHelper; + } + + @Override public FakeAppOpsHelper getAppOpsHelper() { return mAppOpsHelper; } diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 7fc6bbd70000..1f723749856f 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -78,6 +78,7 @@ android_test { "libbinder", "libc++", "libcutils", + "libicing", "liblog", "liblzma", "libnativehelper", diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 90e1cfcd305a..79936ce6d623 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -77,6 +77,7 @@ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/> <uses-permission android:name="android.permission.DUMP"/> <uses-permission android:name="android.permission.READ_DREAM_STATE"/> + <uses-permission android:name="android.permission.READ_DREAM_SUPPRESSION"/> <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"/> diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index b7355ce92c28..b9c2e56a4d90 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -49,6 +49,7 @@ import androidx.test.InstrumentationRegistry; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener; +import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.WindowMagnificationManager; import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.ActivityTaskManagerInternal; @@ -94,6 +95,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { @Mock private IBinder mMockBinder; @Mock private IAccessibilityServiceClient mMockServiceClient; @Mock private WindowMagnificationManager mMockWindowMagnificationMgr; + @Mock private MagnificationController mMockMagnificationController; private AccessibilityUserState mUserState; private MessageCapturingHandler mHandler = new MessageCapturingHandler(null); @@ -110,6 +112,8 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { LocalServices.addService( ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal); + when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn( + mMockWindowMagnificationMgr); mA11yms = new AccessibilityManagerService( InstrumentationRegistry.getContext(), mMockPackageManager, @@ -117,7 +121,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { mMockSystemActionPerformer, mMockA11yWindowManager, mMockA11yDisplayListener, - mMockWindowMagnificationMgr); + mMockMagnificationController); final AccessibilityUserState userState = new AccessibilityUserState( mA11yms.getCurrentUserIdLocked(), mMockContext, mA11yms); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationTransitionControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index cd8e39cfd2e7..3e34f8a428db 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationTransitionControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -62,7 +62,7 @@ import org.mockito.MockitoAnnotations; * Tests for MagnificationController. */ @RunWith(AndroidJUnit4.class) -public class MagnificationTransitionControllerTest { +public class MagnificationControllerTest { private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; private static final Region MAGNIFICATION_REGION = new Region(0, 0, 500, 600); @@ -75,7 +75,7 @@ public class MagnificationTransitionControllerTest { Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; @Mock private AccessibilityManagerService mService; - @Mock private MagnificationTransitionController.TransitionCallBack mTransitionCallBack; + @Mock private MagnificationController.TransitionCallBack mTransitionCallBack; @Mock private Context mContext; @Mock private FullScreenMagnificationController mScreenMagnificationController; @Captor private ArgumentCaptor<MagnificationAnimationCallback> mCallbackArgumentCaptor; @@ -83,7 +83,7 @@ public class MagnificationTransitionControllerTest { private MockWindowMagnificationConnection mMockConnection; private WindowMagnificationManager mWindowMagnificationManager; private MockContentResolver mMockResolver; - private MagnificationTransitionController mMagnificationTransitionController; + private MagnificationController mMagnificationController; @Before public void setUp() throws Exception { @@ -95,14 +95,12 @@ public class MagnificationTransitionControllerTest { Settings.Secure.putFloatForUser(mMockResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, DEFAULT_SCALE, CURRENT_USER_ID); - mWindowMagnificationManager = new WindowMagnificationManager(mContext, CURRENT_USER_ID); - when(mService.getFullScreenMagnificationController()).thenReturn( - mScreenMagnificationController); - when(mService.getWindowMagnificationMgr()).thenReturn(mWindowMagnificationManager); + mWindowMagnificationManager = Mockito.spy( + new WindowMagnificationManager(mContext, CURRENT_USER_ID)); mMockConnection = new MockWindowMagnificationConnection(true); mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mMagnificationTransitionController = new MagnificationTransitionController(mService, - new Object()); + mMagnificationController = new MagnificationController(mService, new Object(), mContext, + mScreenMagnificationController, mWindowMagnificationManager); } @After @@ -113,7 +111,7 @@ public class MagnificationTransitionControllerTest { @Test public void transitionToWindowMode_notMagnifying_doNothing() throws RemoteException { setMagnificationModeSettings(MODE_FULLSCREEN); - mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, MODE_WINDOW, mTransitionCallBack); @@ -130,7 +128,7 @@ public class MagnificationTransitionControllerTest { throws RemoteException { setMagnificationEnabled(MODE_FULLSCREEN); - mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, MODE_WINDOW, mTransitionCallBack); @@ -147,11 +145,11 @@ public class MagnificationTransitionControllerTest { public void transitionToWindowMode_disablingWindowMode_enablingWindowWithFormerCenter() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); - mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, MODE_FULLSCREEN, mTransitionCallBack); - mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, MODE_WINDOW, mTransitionCallBack); @@ -166,7 +164,7 @@ public class MagnificationTransitionControllerTest { throws RemoteException { setMagnificationEnabled(MODE_WINDOW); - mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, MODE_FULLSCREEN, mTransitionCallBack); mMockConnection.invokeCallbacks(); @@ -186,7 +184,7 @@ public class MagnificationTransitionControllerTest { magnificationBounds.bottom + 100); setMagnificationEnabled(MODE_WINDOW, magnifiedCenter.x, magnifiedCenter.y); - mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, MODE_FULLSCREEN, mTransitionCallBack); mMockConnection.invokeCallbacks(); @@ -202,11 +200,11 @@ public class MagnificationTransitionControllerTest { public void transitionToFullScreenMode_disablingFullScreen_enableFullScreenWithFormerCenter() throws RemoteException { setMagnificationEnabled(MODE_FULLSCREEN); - mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, MODE_WINDOW, mTransitionCallBack); - mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, MODE_FULLSCREEN, mTransitionCallBack); @@ -221,7 +219,7 @@ public class MagnificationTransitionControllerTest { public void interruptDuringTransitionToFullScreenMode_windowMagnifying_notifyTransitionFailed() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); - mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, MODE_FULLSCREEN, mTransitionCallBack); @@ -237,7 +235,23 @@ public class MagnificationTransitionControllerTest { verify(mTransitionCallBack).onResult(false); } + @Test + public void onDisplayRemoved_notifyAllModules() { + mMagnificationController.onDisplayRemoved(TEST_DISPLAY); + + verify(mScreenMagnificationController).onDisplayRemoved(TEST_DISPLAY); + verify(mWindowMagnificationManager).onDisplayRemoved(TEST_DISPLAY); + } + + @Test + public void updateUserIdIfNeeded_AllModulesAvailable_setUserId() { + mMagnificationController.updateUserIdIfNeeded(CURRENT_USER_ID); + + verify(mScreenMagnificationController).setUserId(CURRENT_USER_ID); + verify(mWindowMagnificationManager).setUserId(CURRENT_USER_ID); + } private void setMagnificationEnabled(int mode) throws RemoteException { + setMagnificationEnabled(mode, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index 89b0a03a25bb..d5be3ede40d2 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -369,6 +369,16 @@ public class WindowMagnificationManagerTest { assertEquals(mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 200f); } + @Test + public void onDisplayRemoved_enabledOnTestDisplay_disabled() { + mWindowMagnificationManager.requestConnection(true); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f); + + mWindowMagnificationManager.onDisplayRemoved(TEST_DISPLAY); + + assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + } + private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) { final int len = pointersLocation.length; diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/AppSearchImplTest.java new file mode 100644 index 000000000000..24f78308c600 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/AppSearchImplTest.java @@ -0,0 +1,345 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appsearch.external.localbackend; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.expectThrows; + +import android.app.appsearch.exceptions.AppSearchException; + +import com.android.server.appsearch.proto.DocumentProto; +import com.android.server.appsearch.proto.GetOptimizeInfoResultProto; +import com.android.server.appsearch.proto.IndexingConfig; +import com.android.server.appsearch.proto.PropertyConfigProto; +import com.android.server.appsearch.proto.PropertyProto; +import com.android.server.appsearch.proto.ResultSpecProto; +import com.android.server.appsearch.proto.SchemaProto; +import com.android.server.appsearch.proto.SchemaTypeConfigProto; +import com.android.server.appsearch.proto.ScoringSpecProto; +import com.android.server.appsearch.proto.SearchResultProto; +import com.android.server.appsearch.proto.SearchSpecProto; +import com.android.server.appsearch.proto.StatusProto; +import com.android.server.appsearch.proto.TermMatchType; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.Set; + +public class AppSearchImplTest { + @Rule + public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + private AppSearchImpl mAppSearchImpl; + + @Before + public void setUp() throws Exception { + mAppSearchImpl = new AppSearchImpl(mTemporaryFolder.newFolder()); + mAppSearchImpl.initialize(); + } + + /** + * Ensure that we can rewrite an incoming schema type by adding the database as a prefix. While + * also keeping any other existing schema types that may already be part of Icing's persisted + * schema. + */ + @Test + public void testRewriteSchema() throws Exception { + SchemaProto.Builder existingSchemaBuilder = mAppSearchImpl.getSchemaProto().toBuilder(); + + SchemaProto newSchema = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder() + .setSchemaType("Foo").build()) + .addTypes(SchemaTypeConfigProto.newBuilder() + .setSchemaType("TestType") + .addProperties(PropertyConfigProto.newBuilder() + .setPropertyName("subject") + .setDataType(PropertyConfigProto.DataType.Code.STRING) + .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) + .setIndexingConfig( + IndexingConfig.newBuilder() + .setTokenizerType( + IndexingConfig.TokenizerType.Code.PLAIN) + .setTermMatchType(TermMatchType.Code.PREFIX) + .build() + ).build() + ).addProperties(PropertyConfigProto.newBuilder() + .setPropertyName("link") + .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT) + .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) + .setSchemaType("RefType") + .build() + ).build() + ).build(); + + Set<String> newTypes = mAppSearchImpl.rewriteSchema("databaseName", existingSchemaBuilder, + newSchema); + assertThat(newTypes).containsExactly("databaseName/Foo", "databaseName/TestType"); + + SchemaProto expectedSchema = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder() + .setSchemaType("databaseName/Foo").build()) + .addTypes(SchemaTypeConfigProto.newBuilder() + .setSchemaType("databaseName/TestType") + .addProperties(PropertyConfigProto.newBuilder() + .setPropertyName("subject") + .setDataType(PropertyConfigProto.DataType.Code.STRING) + .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) + .setIndexingConfig( + IndexingConfig.newBuilder() + .setTokenizerType( + IndexingConfig.TokenizerType.Code.PLAIN) + .setTermMatchType(TermMatchType.Code.PREFIX) + .build() + ).build() + ).addProperties(PropertyConfigProto.newBuilder() + .setPropertyName("link") + .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT) + .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) + .setSchemaType("databaseName/RefType") + .build() + ).build()) + .build(); + assertThat(existingSchemaBuilder.getTypesList()) + .containsExactlyElementsIn(expectedSchema.getTypesList()); + } + + @Test + public void testRewriteDocumentProto() { + DocumentProto insideDocument = DocumentProto.newBuilder() + .setUri("inside-uri") + .setSchema("type") + .setNamespace("namespace") + .build(); + DocumentProto documentProto = DocumentProto.newBuilder() + .setUri("uri") + .setSchema("type") + .setNamespace("namespace") + .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument)) + .build(); + + DocumentProto expectedInsideDocument = DocumentProto.newBuilder() + .setUri("inside-uri") + .setSchema("databaseName/type") + .setNamespace("databaseName/namespace") + .build(); + DocumentProto expectedDocumentProto = DocumentProto.newBuilder() + .setUri("uri") + .setSchema("databaseName/type") + .setNamespace("databaseName/namespace") + .addProperties(PropertyProto.newBuilder().addDocumentValues(expectedInsideDocument)) + .build(); + + DocumentProto.Builder actualDocument = documentProto.toBuilder(); + mAppSearchImpl.rewriteDocumentTypes("databaseName/", actualDocument, /*add=*/true); + assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto); + mAppSearchImpl.rewriteDocumentTypes("databaseName/", actualDocument, /*add=*/false); + assertThat(actualDocument.build()).isEqualTo(documentProto); + } + + @Test + public void testOptimize() throws Exception { + // Insert schema + SchemaProto schema = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder() + .setSchemaType("type").build()) + .build(); + mAppSearchImpl.setSchema("database", schema, /*forceOverride=*/false); + + // Insert enough documents. + for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT + + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) { + DocumentProto insideDocument = DocumentProto.newBuilder() + .setUri("inside-uri" + i) + .setSchema("type") + .setNamespace("namespace") + .build(); + mAppSearchImpl.putDocument("database", insideDocument); + } + + // Check optimize() will release 0 docs since there is no deletion. + GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResult(); + assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0); + + // delete 999 documents , we will reach the threshold to trigger optimize() in next + // deletion. + for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1; i++) { + mAppSearchImpl.remove("database", "namespace", "inside-uri" + i); + } + + // optimize() still not be triggered since we are in the interval to call getOptimizeInfo() + optimizeInfo = mAppSearchImpl.getOptimizeInfoResult(); + assertThat(optimizeInfo.getOptimizableDocs()) + .isEqualTo(AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1); + + // Keep delete docs, will reach the interval this time and trigger optimize(). + for (int i = AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT; + i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT + + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) { + mAppSearchImpl.remove("database", "namespace", "inside-uri" + i); + } + + // Verify optimize() is triggered + optimizeInfo = mAppSearchImpl.getOptimizeInfoResult(); + assertThat(optimizeInfo.getOptimizableDocs()) + .isLessThan((long) AppSearchImpl.CHECK_OPTIMIZE_INTERVAL); + } + + @Test + public void testRewriteSearchSpec() throws Exception { + SearchSpecProto.Builder searchSpecProto = + SearchSpecProto.newBuilder().setQuery(""); + + // Insert schema + SchemaProto schema = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder() + .setSchemaType("type").build()) + .build(); + mAppSearchImpl.setSchema("database", schema, /*forceOverride=*/false); + // Insert document + DocumentProto insideDocument = DocumentProto.newBuilder() + .setUri("inside-uri") + .setSchema("type") + .setNamespace("namespace") + .build(); + mAppSearchImpl.putDocument("database", insideDocument); + + // Rewrite SearchSpec + mAppSearchImpl.rewriteSearchSpecForNonEmptyDatabase( + "database", searchSpecProto); + assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly("database/type"); + assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly("database/namespace"); + } + + @Test + public void testQueryEmptyDatabase() throws Exception { + SearchResultProto searchResultProto = mAppSearchImpl.query("EmptyDatabase", + SearchSpecProto.getDefaultInstance(), + ResultSpecProto.getDefaultInstance(), ScoringSpecProto.getDefaultInstance()); + assertThat(searchResultProto.getResultsCount()).isEqualTo(0); + assertThat(searchResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.OK); + } + + @Test + public void testRemoveEmptyDatabase_NoExceptionThrown() throws Exception { + mAppSearchImpl.removeByType("EmptyDatabase", "FakeType"); + mAppSearchImpl.removeByNamespace("EmptyDatabase", "FakeNamespace"); + mAppSearchImpl.removeAll("EmptyDatabase"); + } + + @Test + public void testSetSchema() throws Exception { + // Create schemas + SchemaProto schemaProto = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email")).build(); + + // Set schema Email to AppSearch database1 + mAppSearchImpl.setSchema("database1", schemaProto, /*forceOverride=*/false); + + // Create excepted schemaType proto. + SchemaProto exceptedProto = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email")) + .build(); + assertThat(mAppSearchImpl.getSchemaProto().getTypesList()) + .containsExactlyElementsIn(exceptedProto.getTypesList()); + } + + @Test + public void testRemoveSchema() throws Exception { + // Create schemas + SchemaProto schemaProto = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email")) + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Document")).build(); + + // Set schema Email and Document to AppSearch database1 + mAppSearchImpl.setSchema("database1", schemaProto, /*forceOverride=*/false); + + // Create excepted schemaType proto. + SchemaProto exceptedProto = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email")) + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Document")) + .build(); + + // Check both schema Email and Document saved correctly. + assertThat(mAppSearchImpl.getSchemaProto().getTypesList()) + .containsExactlyElementsIn(exceptedProto.getTypesList()); + + // Save only Email this time. + schemaProto = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email")).build(); + + // Check the incompatible error has been thrown. + SchemaProto finalSchemaProto = schemaProto; + AppSearchException e = expectThrows(AppSearchException.class, () -> + mAppSearchImpl.setSchema("database1", finalSchemaProto, /*forceOverride=*/false)); + assertThat(e).hasMessageThat().isEqualTo("Schema is incompatible."); + + // ForceOverride to delete. + mAppSearchImpl.setSchema("database1", finalSchemaProto, /*forceOverride=*/true); + + // Check Document schema is removed. + exceptedProto = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email")) + .build(); + assertThat(mAppSearchImpl.getSchemaProto().getTypesList()) + .containsExactlyElementsIn(exceptedProto.getTypesList()); + } + + @Test + public void testRemoveSchema_differentDataBase() throws Exception { + // Create schemas + SchemaProto emailAndDocSchemaProto = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email")) + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Document")).build(); + + // Set schema Email and Document to AppSearch database1 and 2 + mAppSearchImpl.setSchema("database1", emailAndDocSchemaProto, /*forceOverride=*/false); + mAppSearchImpl.setSchema("database2", emailAndDocSchemaProto, /*forceOverride=*/false); + + // Create excepted schemaType proto. + SchemaProto exceptedProto = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email")) + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Document")) + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email")) + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Document")) + .build(); + + // Check Email and Document is saved in database 1 and 2 correctly. + assertThat(mAppSearchImpl.getSchemaProto().getTypesList()) + .containsExactlyElementsIn(exceptedProto.getTypesList()); + + // Save only Email to database1 this time. + SchemaProto emailSchemaProto = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email")) + .build(); + mAppSearchImpl.setSchema("database1", emailSchemaProto, /*forceOverride=*/true); + + // Create excepted schemaType list, database 1 should only contain Email but database 2 + // remains in same. + exceptedProto = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email")) + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email")) + .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Document")) + .build(); + + // Check nothing changed in database2. + assertThat(mAppSearchImpl.getSchemaProto().getTypesList()) + .containsExactlyElementsIn(exceptedProto.getTypesList()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java deleted file mode 100644 index 8986cbae262d..000000000000 --- a/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.server.appsearch.impl; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.expectThrows; - -import android.annotation.UserIdInt; -import android.content.Context; -import android.os.UserHandle; - -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import com.android.server.appsearch.proto.IndexingConfig; -import com.android.server.appsearch.proto.PropertyConfigProto; -import com.android.server.appsearch.proto.SchemaProto; -import com.android.server.appsearch.proto.SchemaTypeConfigProto; -import com.android.server.appsearch.proto.TermMatchType; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class AppSearchImplTest { - private final Context mContext = InstrumentationRegistry.getContext(); - private final @UserIdInt int mUserId = UserHandle.getCallingUserId(); - - @Test - public void testRewriteSchemaTypes() { - SchemaProto inSchema = SchemaProto.newBuilder() - .addTypes(SchemaTypeConfigProto.newBuilder() - .setSchemaType("TestType") - .addProperties(PropertyConfigProto.newBuilder() - .setPropertyName("subject") - .setDataType(PropertyConfigProto.DataType.Code.STRING) - .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) - .setIndexingConfig( - IndexingConfig.newBuilder() - .setTokenizerType( - IndexingConfig.TokenizerType.Code.PLAIN) - .setTermMatchType(TermMatchType.Code.PREFIX) - .build() - ).build() - ).addProperties(PropertyConfigProto.newBuilder() - .setPropertyName("link") - .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT) - .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) - .setSchemaType("RefType") - .build() - ).build() - ).build(); - - SchemaProto expectedSchema = SchemaProto.newBuilder() - .addTypes(SchemaTypeConfigProto.newBuilder() - .setSchemaType("com.android.server.appsearch.impl@42:TestType") - .addProperties(PropertyConfigProto.newBuilder() - .setPropertyName("subject") - .setDataType(PropertyConfigProto.DataType.Code.STRING) - .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) - .setIndexingConfig( - IndexingConfig.newBuilder() - .setTokenizerType( - IndexingConfig.TokenizerType.Code.PLAIN) - .setTermMatchType(TermMatchType.Code.PREFIX) - .build() - ).build() - ).addProperties(PropertyConfigProto.newBuilder() - .setPropertyName("link") - .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT) - .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) - .setSchemaType("com.android.server.appsearch.impl@42:RefType") - .build() - ).build() - ).build(); - - AppSearchImpl impl = new AppSearchImpl(mContext, mUserId); - SchemaProto.Builder actualSchema = inSchema.toBuilder(); - impl.rewriteSchemaTypes("com.android.server.appsearch.impl@42:", actualSchema); - - assertThat(actualSchema.build()).isEqualTo(expectedSchema); - } - - @Test - public void testPackageNotFound() { - AppSearchImpl impl = new AppSearchImpl(mContext, mUserId); - IllegalStateException e = expectThrows( - IllegalStateException.class, - () -> impl.setSchema( - /*callingUid=*/Integer.MAX_VALUE, - SchemaProto.getDefaultInstance(), - /*forceOverride=*/false)); - assertThat(e).hasMessageThat().contains("Failed to look up package name"); - } -} diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java deleted file mode 100644 index 3196fbee8a02..000000000000 --- a/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.server.appsearch.impl; - -import static com.google.common.truth.Truth.assertThat; - -import androidx.test.runner.AndroidJUnit4; - -import com.android.server.appsearch.proto.DocumentProto; -import com.android.server.appsearch.proto.PropertyProto; -import com.android.server.appsearch.proto.SearchResultProto; -import com.android.server.appsearch.proto.StatusProto; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(AndroidJUnit4.class) -public class FakeIcingTest { - @Test - public void query() { - FakeIcing icing = new FakeIcing(); - icing.put(createDoc("uri:cat", "The cat said meow")); - icing.put(createDoc("uri:dog", "The dog said woof")); - - assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat"); - assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog"); - assertThat(queryGetUris(icing, "fred")).isEmpty(); - } - - @Test - public void queryNorm() { - FakeIcing icing = new FakeIcing(); - icing.put(createDoc("uri:cat", "The cat said meow")); - icing.put(createDoc("uri:dog", "The dog said woof")); - - assertThat(queryGetUris(icing, "the")).containsExactly("uri:cat", "uri:dog"); - assertThat(queryGetUris(icing, "The")).containsExactly("uri:cat", "uri:dog"); - assertThat(queryGetUris(icing, "tHe")).containsExactly("uri:cat", "uri:dog"); - } - - @Test - public void get() { - DocumentProto cat = createDoc("uri:cat", "The cat said meow"); - FakeIcing icing = new FakeIcing(); - icing.put(cat); - assertThat(icing.get("uri:cat")).isEqualTo(cat); - } - - @Test - public void replace() { - DocumentProto cat = createDoc("uri:cat", "The cat said meow"); - DocumentProto dog = createDoc("uri:dog", "The dog said woof"); - - FakeIcing icing = new FakeIcing(); - icing.put(cat); - icing.put(dog); - - assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat"); - assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog"); - assertThat(icing.get("uri:cat")).isEqualTo(cat); - - // Replace - DocumentProto cat2 = createDoc("uri:cat", "The cat said purr"); - DocumentProto bird = createDoc("uri:bird", "The cat said tweet"); - icing.put(cat2); - icing.put(bird); - - assertThat(queryGetUris(icing, "meow")).isEmpty(); - assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog", "uri:bird"); - assertThat(icing.get("uri:cat")).isEqualTo(cat2); - } - - @Test - public void delete() { - DocumentProto cat = createDoc("uri:cat", "The cat said meow"); - DocumentProto dog = createDoc("uri:dog", "The dog said woof"); - - FakeIcing icing = new FakeIcing(); - icing.put(cat); - icing.put(dog); - - assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat"); - assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog"); - assertThat(icing.get("uri:cat")).isEqualTo(cat); - - // Delete - icing.delete("uri:cat"); - icing.delete("uri:notreal"); - - assertThat(queryGetUris(icing, "meow")).isEmpty(); - assertThat(queryGetUris(icing, "said")).containsExactly("uri:dog"); - assertThat(icing.get("uri:cat")).isNull(); - } - - private static DocumentProto createDoc(String uri, String body) { - return DocumentProto.newBuilder() - .setUri(uri) - .addProperties(PropertyProto.newBuilder().addStringValues(body)) - .build(); - } - - private static List<String> queryGetUris(FakeIcing icing, String term) { - List<String> uris = new ArrayList<>(); - SearchResultProto results = icing.query(term); - assertThat(results.getStatus().getCode()).isEqualTo(StatusProto.Code.OK); - for (SearchResultProto.ResultProto result : results.getResultsList()) { - uris.add(result.getDocument().getUri()); - } - return uris; - } -} diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 73dda0736d2f..da25fd612463 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -309,7 +309,8 @@ public class DisplayManagerServiceTest { zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect); displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY; displayDevice.setDisplayDeviceInfo(displayDeviceInfo); - displayManager.handleDisplayDeviceAdded(displayDevice); + displayManager.getDisplayDeviceRepository() + .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); // Find the display id of the added FakeDisplayDevice DisplayManagerService.BinderService bs = displayManager.new BinderService(); diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java index 301a9fe64d5e..b312e52ff74b 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java @@ -28,8 +28,6 @@ import android.view.SurfaceControl; import org.junit.Before; import org.junit.Test; -import java.util.ArrayList; - public class LogicalDisplayTest { private static final int DISPLAY_ID = 0; private static final int LAYER_STACK = 0; @@ -51,9 +49,16 @@ public class LogicalDisplayTest { mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice); when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(displayDeviceInfo); - ArrayList<DisplayDevice> displayDevices = new ArrayList<>(); - displayDevices.add(mDisplayDevice); - mLogicalDisplay.updateLocked(displayDevices); + DisplayDeviceRepository repo = new DisplayDeviceRepository( + new DisplayManagerService.SyncRoot(), new DisplayDeviceRepository.Listener() { + @Override + public void onDisplayDeviceEventLocked(DisplayDevice device, int event) {} + + @Override + public void onTraversalRequested() {} + }); + repo.onDisplayDeviceEvent(mDisplayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); + mLogicalDisplay.updateLocked(repo); } @Test diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java index 08f558e6d99c..1385376b740d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -54,8 +54,6 @@ public class ArcInitiationActionFromAvrTest { private Context mContextSpy; private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem; - private HdmiCecController mHdmiCecController; - private HdmiControlService mHdmiControlService; private FakeNativeWrapper mNativeWrapper; private ArcInitiationActionFromAvr mAction; @@ -78,7 +76,7 @@ public class ArcInitiationActionFromAvrTest { when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); when(mIPowerManagerMock.isInteractive()).thenReturn(true); - mHdmiControlService = + HdmiControlService hdmiControlService = new HdmiControlService(mContextSpy) { @Override boolean isPowerStandby() { @@ -110,7 +108,7 @@ public class ArcInitiationActionFromAvrTest { } }; - mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService) { + mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { @Override protected void setPreferredAddress(int addr) { } @@ -118,18 +116,18 @@ public class ArcInitiationActionFromAvrTest { mHdmiCecLocalDeviceAudioSystem.init(); Looper looper = mTestLooper.getLooper(); - mHdmiControlService.setIoLooper(looper); + hdmiControlService.setIoLooper(looper); mNativeWrapper = new FakeNativeWrapper(); - mHdmiCecController = HdmiCecController.createWithNativeWrapper( - this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); - mHdmiControlService.setCecController(mHdmiCecController); - mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); - mHdmiControlService.initPortInfo(); + HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( + hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter()); + hdmiControlService.setCecController(hdmiCecController); + hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService)); + hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService)); + hdmiControlService.initPortInfo(); mAction = new ArcInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem); mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + hdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); } @@ -142,7 +140,7 @@ public class ArcInitiationActionFromAvrTest { assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc); - mHdmiControlService.sendCecCommand( + mNativeWrapper.onCecMessage( HdmiCecMessageBuilder.buildReportArcInitiated( Constants.ADDR_TV, Constants.ADDR_AUDIO_SYSTEM)); @@ -174,7 +172,7 @@ public class ArcInitiationActionFromAvrTest { assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc); - mHdmiControlService.handleCecCommand(HdmiCecMessageBuilder.buildReportArcTerminated( + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportArcTerminated( Constants.ADDR_TV, Constants.ADDR_AUDIO_SYSTEM)); mTestLooper.dispatchAll(); @@ -192,7 +190,7 @@ public class ArcInitiationActionFromAvrTest { assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc); - mHdmiControlService.handleCecCommand( + mNativeWrapper.onCecMessage( HdmiCecMessageBuilder.buildFeatureAbortCommand( Constants.ADDR_TV, Constants.ADDR_AUDIO_SYSTEM, Constants.MESSAGE_INITIATE_ARC, diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index 4afbbf741102..169f885a7253 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -56,8 +56,6 @@ public class ArcTerminationActionFromAvrTest { private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem; private ArcTerminationActionFromAvr mAction; - private HdmiCecController mHdmiCecController; - private HdmiControlService mHdmiControlService; private FakeNativeWrapper mNativeWrapper; private TestLooper mTestLooper = new TestLooper(); @@ -79,7 +77,7 @@ public class ArcTerminationActionFromAvrTest { when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); when(mIPowerManagerMock.isInteractive()).thenReturn(true); - mHdmiControlService = + HdmiControlService hdmiControlService = new HdmiControlService(mContextSpy) { @Override void wakeUp() { @@ -112,16 +110,16 @@ public class ArcTerminationActionFromAvrTest { }; Looper looper = mTestLooper.getLooper(); - mHdmiControlService.setIoLooper(looper); + hdmiControlService.setIoLooper(looper); mNativeWrapper = new FakeNativeWrapper(); - mHdmiCecController = HdmiCecController.createWithNativeWrapper( - this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); - mHdmiControlService.setCecController(mHdmiCecController); - mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); - mHdmiControlService.initPortInfo(); - - mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService) { + HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( + hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter()); + hdmiControlService.setCecController(hdmiCecController); + hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService)); + hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService)); + hdmiControlService.initPortInfo(); + + mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { @Override protected void setPreferredAddress(int addr) { } @@ -130,7 +128,7 @@ public class ArcTerminationActionFromAvrTest { mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem); mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + hdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mHdmiCecLocalDeviceAudioSystem.setArcStatus(true); mTestLooper.dispatchAll(); } @@ -173,7 +171,7 @@ public class ArcTerminationActionFromAvrTest { HdmiCecMessage arcTerminatedResponse = HdmiCecMessageBuilder.buildReportArcTerminated( Constants.ADDR_TV, Constants.ADDR_AUDIO_SYSTEM); - mHdmiControlService.handleCecCommand(arcTerminatedResponse); + mNativeWrapper.onCecMessage(arcTerminatedResponse); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java index 01f0a3d398df..2c42791fabce 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java @@ -16,7 +16,11 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.tv.cec.V1_0.CecMessage; +import android.hardware.tv.cec.V1_0.HotplugEvent; import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.os.RemoteException; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.hdmi.HdmiCecController.NativeWrapper; @@ -29,6 +33,8 @@ import java.util.List; /** Fake {@link NativeWrapper} useful for testing. */ final class FakeNativeWrapper implements NativeWrapper { + private static final String TAG = "FakeNativeWrapper"; + private final int[] mPollAddressResponse = new int[] { SendMessageResult.NACK, @@ -52,6 +58,7 @@ final class FakeNativeWrapper implements NativeWrapper { private final HashMap<Integer, Integer> mMessageSendResult = new HashMap<>(); private int mMyPhysicalAddress = 0; private HdmiPortInfo[] mHdmiPortInfo = null; + private HdmiCecController.HdmiCecCallback mCallback = null; @Override public String nativeInit() { @@ -59,7 +66,9 @@ final class FakeNativeWrapper implements NativeWrapper { } @Override - public void setCallback(HdmiCecController.HdmiCecCallback callback) {} + public void setCallback(HdmiCecController.HdmiCecCallback callback) { + this.mCallback = callback; + } @Override public int nativeSendCecCommand( @@ -119,6 +128,42 @@ final class FakeNativeWrapper implements NativeWrapper { return false; } + public void onCecMessage(HdmiCecMessage hdmiCecMessage) { + if (mCallback == null) { + return; + } + CecMessage message = new CecMessage(); + message.initiator = hdmiCecMessage.getSource(); + message.destination = hdmiCecMessage.getDestination(); + ArrayList<Byte> body = new ArrayList<>(); + body.add((byte) hdmiCecMessage.getOpcode()); + for (byte param : hdmiCecMessage.getParams()) { + body.add(param); + } + message.body = body; + try { + mCallback.onCecMessage(message); + } catch (RemoteException e) { + Log.e(TAG, "Error sending CEC message", e); + } + } + + public void onHotplugEvent(int port, boolean connected) { + if (mCallback == null) { + return; + } + + HotplugEvent hotplugEvent = new HotplugEvent(); + hotplugEvent.portId = port; + hotplugEvent.connected = connected; + + try { + mCallback.onHotplugEvent(hotplugEvent); + } catch (RemoteException e) { + Log.e(TAG, "Error sending hotplug event", e); + } + } + public List<HdmiCecMessage> getResultMessages() { return new ArrayList<>(mResultMessages); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java index c60d5fb95846..6e7ec2a88140 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java @@ -70,7 +70,6 @@ public class HdmiCecControllerTest { } } - private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; private int mLogicalAddress = 16; private AllocateAddressCallback mCallback = @@ -87,10 +86,11 @@ public class HdmiCecControllerTest { public void SetUp() { mMyLooper = mTestLooper.getLooper(); mMyLooper = mTestLooper.getLooper(); - mHdmiControlService = new MyHdmiControlService(InstrumentationRegistry.getTargetContext()); + HdmiControlService hdmiControlService = new MyHdmiControlService( + InstrumentationRegistry.getTargetContext()); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper( - mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); + hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter()); } /** Tests for {@link HdmiCecController#allocateLogicalAddress} */ diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index d160a3fab186..498ebf4a2ef9 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -39,7 +39,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -370,16 +369,11 @@ public class HdmiCecLocalDevicePlaybackTest { assertThat(mStandby).isFalse(); } - // Playback device does not handle routing control related feature right now - @Ignore("b/120845532") @Test - public void handleSetStreamPath_underCurrentDevice() { - assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(0); + public void handleSetStreamPath() { HdmiCecMessage message = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100); assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue(); - // TODO(amyjojo): Move set and get LocalActivePath to Control Service. - assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(1); } @Test @@ -786,8 +780,8 @@ public class HdmiCecLocalDevicePlaybackTest { @Test public void handleSetStreamPath_afterHotplug_broadcastsActiveSource() { - mHdmiControlService.onHotplug(1, false); - mHdmiControlService.onHotplug(1, true); + mNativeWrapper.onHotplugEvent(1, false); + mNativeWrapper.onHotplugEvent(1, true); HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress); @@ -803,8 +797,8 @@ public class HdmiCecLocalDevicePlaybackTest { @Test public void handleSetStreamPath_afterHotplug_hasCorrectActiveSource() { - mHdmiControlService.onHotplug(1, false); - mHdmiControlService.onHotplug(1, true); + mNativeWrapper.onHotplugEvent(1, false); + mNativeWrapper.onHotplugEvent(1, true); HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress); 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 631b8d3cb1ea..9ee9259a23c2 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 @@ -15,17 +15,22 @@ */ package com.android.server.location.timezone; +import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE; import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_SUCCESS; import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED; import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED; import static com.android.server.location.timezone.TestSupport.USER1_ID; import static com.android.server.location.timezone.TestSupport.USER2_CONFIG_GEO_DETECTION_ENABLED; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -39,6 +44,7 @@ import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.util.IndentingPrintWriter; +import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; import com.android.server.timezonedetector.TestState; @@ -65,10 +71,12 @@ public class ControllerImplTest { createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_SUCCESS, asList("Europe/Paris")); private static final LocationTimeZoneEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT = createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_UNCERTAIN, null); + private static final LocationTimeZoneEvent USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT = + createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_PERMANENT_FAILURE, null); private TestThreadingDomain mTestThreadingDomain; private TestCallback mTestCallback; - private TestLocationTimeZoneProvider mTestLocationTimeZoneProvider; + private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider; @Before public void setUp() { @@ -77,276 +85,446 @@ public class ControllerImplTest { // will never get a chance to execute. mTestThreadingDomain = new TestThreadingDomain(); mTestCallback = new TestCallback(mTestThreadingDomain); - mTestLocationTimeZoneProvider = + mTestPrimaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider(mTestThreadingDomain, "primary"); } @Test public void initialState_enabled() { - ControllerImpl controllerImpl = - new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider); + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout() + .plus(testEnvironment.getProviderInitializationTimeoutFuzz()); + + // Initialize. After initialization the provider must be initialized and should be + // enabled. controllerImpl.initialize(testEnvironment, mTestCallback); - mTestLocationTimeZoneProvider.assertInitialized(); + mTestPrimaryLocationTimeZoneProvider.assertInitialized(); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - Duration expectedTimeout = expectedProviderInitializationTimeout(); - mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @Test public void initialState_disabled() { - ControllerImpl controllerImpl = - new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider); + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED); + + // Initialize. After initialization the provider must be initialized but should not be + // enabled. controllerImpl.initialize(testEnvironment, mTestCallback); - mTestLocationTimeZoneProvider.assertInitialized(); + mTestPrimaryLocationTimeZoneProvider.assertInitialized(); - mTestLocationTimeZoneProvider.assertIsDisabled(); - mTestThreadingDomain.assertQueueEmpty(); + mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @Test public void enabled_uncertaintySuggestionSentIfNoEventReceived() { - ControllerImpl controllerImpl = - new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider); + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. controllerImpl.initialize(testEnvironment, mTestCallback); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate time passing with no provider event being received from the primary. + mTestThreadingDomain.executeNext(); + + // The primary should have reported uncertainty, which should trigger the controller to + // start the uncertainty timeout. + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); - Duration expectedTimeout = expectedProviderInitializationTimeout(); - mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); - // Simulate time passing with no event being received. + // Finally, the uncertainty timeout should cause the controller to make an uncertain + // suggestion. mTestThreadingDomain.executeNext(); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertUncertainSuggestionMadeAndCommit(); - mTestThreadingDomain.assertQueueEmpty(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @Test - public void enabled_uncertaintySuggestionCancelledIfEventReceived() { - ControllerImpl controllerImpl = - new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider); + public void enabled_eventReceivedBeforeInitializationTimeout() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. controllerImpl.initialize(testEnvironment, mTestCallback); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - Duration expectedTimeout = expectedProviderInitializationTimeout(); - mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); - // Simulate a location event being received by the provider. This should cause a suggestion - // to be made, and the timeout to be cleared. - mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + // Simulate a location event being received from the primary provider. This should cause a + // suggestion to be made. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - mTestThreadingDomain.assertQueueEmpty(); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @Test - public void enabled_repeatedCertainty() { - ControllerImpl controllerImpl = - new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider); + public void enabled_eventReceivedFromPrimaryAfterInitializationTimeout() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate time passing with no provider event being received from the primary. + mTestThreadingDomain.executeNext(); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + + // Simulate a location event being received from the primary provider. This should cause a + // suggestion to be made. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertSuggestionMadeAndCommit( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + @Test + public void enabled_repeatedPrimaryCertainty() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. controllerImpl.initialize(testEnvironment, mTestCallback); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - Duration expectedTimeout = expectedProviderInitializationTimeout(); - mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); - // Simulate a location event being received by the provider. This should cause a suggestion - // to be made, and the timeout to be cleared. - mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + // Simulate a location event being received from the primary provider. This should cause a + // suggestion to be made. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - mTestThreadingDomain.assertQueueEmpty(); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); // A second, identical event should not cause another suggestion. - mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - mTestThreadingDomain.assertQueueEmpty(); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); // And a third, different event should cause another suggestion. - mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - mTestThreadingDomain.assertQueueEmpty(); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + @Test + public void enabled_uncertaintyTriggersASuggestionAfterUncertaintyTimeout() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate a location event being received from the primary provider. This should cause a + // suggestion to be made and ensure the primary is considered initialized. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertSuggestionMadeAndCommit( + USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate an uncertain event being received from the primary provider. This should not + // cause a suggestion to be made straight away, but the uncertainty timeout should be + // started. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + + // Simulate time passing. This means the uncertainty timeout should fire and the uncertain + // suggestion should be made. + mTestThreadingDomain.executeNext(); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertUncertainSuggestionMadeAndCommit(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @Test public void enabled_briefUncertaintyTriggersNoSuggestion() { - ControllerImpl controllerImpl = - new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider); + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. controllerImpl.initialize(testEnvironment, mTestCallback); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - Duration expectedTimeout = expectedProviderInitializationTimeout(); - mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); - // Simulate a location event being received by the provider. This should cause a suggestion - // to be made, and the timeout to be cleared. - mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + // Simulate a location event being received from the primary provider. This should cause a + // suggestion to be made. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - mTestThreadingDomain.assertQueueEmpty(); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); - // Uncertainty should cause a suggestion to (only) be queued. - mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + // Uncertainty should not cause a suggestion to be made straight away, but the uncertainty + // timeout should be started. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - mTestThreadingDomain.assertSingleDelayedQueueItem(testEnvironment.getUncertaintyDelay()); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); + assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); - // And a third event should cause yet another suggestion and for the queued item to be - // removed. - mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + // And a success event from the primary provider should cause the controller to make another + // suggestion, the uncertainty timeout should be cancelled. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - mTestThreadingDomain.assertQueueEmpty(); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @Test - public void configChanges_enableAndDisable() { - ControllerImpl controllerImpl = - new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider); + public void configChanges_enableAndDisableWithNoPreviousSuggestion() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED); + + // Initialize and check initial state. controllerImpl.initialize(testEnvironment, mTestCallback); - mTestLocationTimeZoneProvider.assertIsDisabled(); - mTestThreadingDomain.assertQueueEmpty(); + mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is enabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - Duration expectedTimeout = expectedProviderInitializationTimeout(); - mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is disabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); - mTestLocationTimeZoneProvider.assertIsDisabled(); - mTestThreadingDomain.assertQueueEmpty(); + mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @Test - public void configChanges_disableWithPreviousSuggestion() { - ControllerImpl controllerImpl = - new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider); + public void configChanges_enableAndDisableWithPreviousSuggestion() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED); + + // Initialize and check initial state. controllerImpl.initialize(testEnvironment, mTestCallback); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - Duration expectedTimeout = expectedProviderInitializationTimeout(); - mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout); + mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Now signal a config change so that geo detection is enabled. + testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); - // Simulate a location event being received by the provider. This should cause a suggestion - // to be made, and the timeout to be cleared. - mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Simulate a success event being received from the primary provider. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - mTestThreadingDomain.assertQueueEmpty(); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); - // Simulate the user disabling the provider. - testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); - + // Now signal a config change so that geo detection is disabled. // Because there had been a previous suggestion, the controller should withdraw it // immediately to let the downstream components know that the provider can no longer be sure // of the time zone. - mTestLocationTimeZoneProvider.assertIsDisabled(); - mTestThreadingDomain.assertQueueEmpty(); - mTestCallback.assertSuggestionMadeAndCommit(null); + testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); + + mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit(); + mTestCallback.assertUncertainSuggestionMadeAndCommit(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); } @Test public void configChanges_userSwitch_enabledToEnabled() { - ControllerImpl controllerImpl = - new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider); + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider); TestEnvironment testEnvironment = new TestEnvironment( mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. controllerImpl.initialize(testEnvironment, mTestCallback); - // There should be a runnable scheduled to suggest uncertainty if no event is received. - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - Duration expectedTimeout = expectedProviderInitializationTimeout(); - mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); - // Have the provider suggest a time zone. - mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + // Simulate the primary provider suggesting a time zone. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); // Receiving a "success" provider event should cause a suggestion to be made synchronously, // and also clear the scheduled uncertainty suggestion. - mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED); - mTestThreadingDomain.assertQueueEmpty(); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertSuggestionMadeAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds()); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); // Simulate the user change (but geo detection still enabled). testEnvironment.simulateConfigChange(USER2_CONFIG_GEO_DETECTION_ENABLED); // We expect the provider to end up in PROVIDER_STATE_ENABLED, but it should have been // disabled when the user changed. - // The controller should schedule a runnable to make a suggestion if the provider doesn't - // send a success event. - int[] expectedStateTransitions = { PROVIDER_STATE_DISABLED, PROVIDER_STATE_ENABLED }; - mTestLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions); - mTestLocationTimeZoneProvider.assertConfig(USER2_CONFIG_GEO_DETECTION_ENABLED); - expectedTimeout = expectedProviderInitializationTimeout(); - mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout); + int[] expectedStateTransitions = + { PROVIDER_STATE_DISABLED, PROVIDER_STATE_ENABLED_INITIALIZING }; + mTestPrimaryLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions); + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig( + PROVIDER_STATE_ENABLED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + @Test + public void primaryPermFailure_disableAndEnable() { + ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, + mTestPrimaryLocationTimeZoneProvider); + TestEnvironment testEnvironment = new TestEnvironment( + mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + + // Initialize and check initial state. + controllerImpl.initialize(testEnvironment, mTestCallback); + + mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( + PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); - // Simulate no event being received, and time passing. - mTestThreadingDomain.executeNext(); + // Simulate a failure location event being received from the primary provider. This should + // cause an uncertain suggestion to be made. + mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent( + USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); - mTestLocationTimeZoneProvider.assertIsEnabled(USER2_CONFIG_GEO_DETECTION_ENABLED); - mTestThreadingDomain.assertQueueEmpty(); + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestCallback.assertUncertainSuggestionMadeAndCommit(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Now signal a config change so that geo detection is disabled. + testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); + + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestCallback.assertNoSuggestionMade(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + + // Now signal a config change so that geo detection is enabled. + testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); + + mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestCallback.assertUncertainSuggestionMadeAndCommit(); + assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + } + + private static void assertUncertaintyTimeoutSet( + LocationTimeZoneProviderController.Environment environment, + LocationTimeZoneProviderController controller) { + assertTrue(controller.isUncertaintyTimeoutSet()); + assertEquals(environment.getUncertaintyDelay().toMillis(), + controller.getUncertaintyTimeoutDelayMillis()); } private static LocationTimeZoneEvent createLocationTimeZoneEvent(@UserIdInt int userId, @@ -362,16 +540,15 @@ public class ControllerImplTest { } - private Duration expectedProviderInitializationTimeout() { - return TestEnvironment.PROVIDER_INITIALIZATION_TIMEOUT - .plus(TestEnvironment.PROVIDER_INITIALIZATION_TIMEOUT_FUZZ); - } - private static class TestEnvironment extends LocationTimeZoneProviderController.Environment { + // These timeouts are set deliberately so that: + // (initialization timeout * 2) < uncertainty delay + // + // That makes the order of initialization timeout Vs uncertainty delay deterministic. static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5); static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1); - private static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(3); + private static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(15); private final LocationTimeZoneProviderController mController; private ConfigurationInternal mConfigurationInternal; @@ -491,38 +668,57 @@ public class ControllerImplTest { assertTrue(mInitialized); } - void assertIsDisabled() { - // Disabled providers don't hold config. - assertConfig(null); - assertIsEnabledAndCommit(false); + public void assertIsPermFailedAndCommit() { + // A failed provider doesn't hold config. + assertStateEnumAndConfig(PROVIDER_STATE_PERM_FAILED, null /* config */); + mTestProviderState.commitLatest(); + } + + void assertIsDisabledAndCommit() { + // A disabled provider doesn't hold config. + assertStateEnumAndConfig(PROVIDER_STATE_DISABLED, null /* config */); + mTestProviderState.commitLatest(); } /** - * Asserts the provider's config matches the expected, and the current state is set - * accordingly. Commits the latest changes to the state. + * Asserts the provider's state enum and config matches the expected. + * Commits the latest changes to the state. */ - void assertIsEnabled(@NonNull ConfigurationInternal expectedConfig) { - assertConfig(expectedConfig); - - boolean expectIsEnabled = expectedConfig.getAutoDetectionEnabledBehavior(); - assertIsEnabledAndCommit(expectIsEnabled); + void assertStateEnumAndConfigAndCommit( + @ProviderStateEnum int expectedStateEnum, + @Nullable ConfigurationInternal expectedConfig) { + assertStateEnumAndConfig(expectedStateEnum, expectedConfig); + mTestProviderState.commitLatest(); } - private void assertIsEnabledAndCommit(boolean enabled) { + /** + * Asserts the provider's state enum and config matches the expected. + * Does not commit any state changes. + */ + void assertStateEnumAndConfig( + @ProviderStateEnum int expectedStateEnum, + @Nullable ConfigurationInternal expectedConfig) { ProviderState currentState = mCurrentState.get(); - if (enabled) { - assertEquals(PROVIDER_STATE_ENABLED, currentState.stateEnum); - } else { - assertEquals(PROVIDER_STATE_DISABLED, currentState.stateEnum); - } - mTestProviderState.commitLatest(); + assertEquals(expectedStateEnum, currentState.stateEnum); + + // If and only if the controller is initializing, the initialization timeout must be + // set. + assertEquals(expectedStateEnum == PROVIDER_STATE_ENABLED_INITIALIZING, + isInitializationTimeoutSet()); + + assertConfig(expectedConfig); } - void assertConfig(@NonNull ConfigurationInternal expectedConfig) { + private void assertConfig(@Nullable ConfigurationInternal expectedConfig) { ProviderState currentState = mCurrentState.get(); assertEquals(expectedConfig, currentState.currentUserConfiguration); } + void assertInitializationTimeoutSet(Duration expectedTimeout) { + assertTrue(isInitializationTimeoutSet()); + assertEquals(expectedTimeout, getInitializationTimeoutDelay()); + } + void simulateLocationTimeZoneEvent(@NonNull LocationTimeZoneEvent event) { handleLocationTimeZoneEvent(event); } diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java index cbaf0f391375..4d6775dcaecf 100644 --- a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java +++ b/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java @@ -115,28 +115,7 @@ public class HandlerThreadingDomainTest { } @Test - public void singleRunnableHandler_runSynchronously() throws Exception { - ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler); - SingleRunnableQueue singleRunnableQueue = domain.createSingleRunnableQueue(); - - AtomicBoolean testPassed = new AtomicBoolean(false); - // Calls to SingleRunnableQueue must be made on the handler thread it is associated with, - // so this uses runWithScissors() to block until the lambda has completed. - mTestHandler.runWithScissors(() -> { - Thread testThread = Thread.currentThread(); - CountDownLatch latch = new CountDownLatch(1); - singleRunnableQueue.runSynchronously(() -> { - assertSame(Thread.currentThread(), testThread); - latch.countDown(); - }); - assertTrue(awaitWithRuntimeException(latch, 60, TimeUnit.SECONDS)); - testPassed.set(true); - }, TimeUnit.SECONDS.toMillis(60)); - assertTrue(testPassed.get()); - } - - @Test - public void singleRunnableHandler_runDelayed() throws Exception { + public void singleRunnableQueue_runDelayed() throws Exception { ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler); SingleRunnableQueue singleRunnableQueue = domain.createSingleRunnableQueue(); diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java index 5542db0b5ae3..5403a654f7e4 100644 --- a/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java @@ -16,7 +16,7 @@ package com.android.server.location.timezone; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED; +import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED; @@ -76,15 +76,16 @@ public class NullLocationTimeZoneProviderTest { ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED; Duration arbitraryInitializationTimeout = Duration.ofMinutes(5); - provider.enable(config, arbitraryInitializationTimeout); + Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2); + provider.enable(config, arbitraryInitializationTimeout, arbitraryInitializationTimeoutFuzz); - // The StubbedProvider should enters enabled state, but immediately schedule a runnable to - // switch to perm failure. + // The NullProvider should enter the enabled state, but have schedule an immediate runnable + // to switch to perm failure. ProviderState currentState = provider.getCurrentState(); assertSame(provider, currentState.provider); - assertEquals(PROVIDER_STATE_ENABLED, currentState.stateEnum); + assertEquals(PROVIDER_STATE_ENABLED_INITIALIZING, currentState.stateEnum); assertEquals(config, currentState.currentUserConfiguration); - mTestThreadingDomain.assertSingleImmediateQueueItem(); + mTestThreadingDomain.assertNextQueueItemIsImmediate(); // Entering enabled() does not trigger an onProviderStateChanged() as it is requested by the // controller. mTestController.assertProviderChangeNotTriggered(); @@ -116,6 +117,18 @@ public class NullLocationTimeZoneProviderTest { // Not needed for provider testing. } + @Override + boolean isUncertaintyTimeoutSet() { + // Not needed for provider testing. + return false; + } + + @Override + long getUncertaintyTimeoutDelayMillis() { + // Not needed for provider testing. + return 0; + } + void onProviderStateChange(ProviderState providerState) { this.mProviderState.set(providerState); } diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java index def919e79731..7359abd11c3a 100644 --- a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java +++ b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java @@ -16,13 +16,15 @@ package com.android.server.location.timezone; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.annotation.NonNull; import android.annotation.Nullable; import java.time.Duration; -import java.util.LinkedList; +import java.util.ArrayList; +import java.util.Comparator; import java.util.Objects; /** @@ -33,6 +35,10 @@ import java.util.Objects; class TestThreadingDomain extends ThreadingDomain { static class QueuedRunnable { + + static final Comparator<? super QueuedRunnable> COMPARATOR = + (o1, o2) -> (int) (o1.executionTimeMillis - o2.executionTimeMillis); + @NonNull public final Runnable runnable; @Nullable public final Object token; public final long executionTimeMillis; @@ -55,7 +61,7 @@ class TestThreadingDomain extends ThreadingDomain { } private long mCurrentTimeMillis; - private LinkedList<QueuedRunnable> mQueue = new LinkedList<>(); + private ArrayList<QueuedRunnable> mQueue = new ArrayList<>(); TestThreadingDomain() { // Pick an arbitrary time. @@ -69,22 +75,23 @@ class TestThreadingDomain extends ThreadingDomain { @Override void post(Runnable r) { - mQueue.add(new QueuedRunnable(r, null, mCurrentTimeMillis)); + postDelayed(r, null, 0); } @Override void postDelayed(Runnable r, long delayMillis) { - mQueue.add(new QueuedRunnable(r, null, mCurrentTimeMillis + delayMillis)); + postDelayed(r, null, delayMillis); } @Override void postDelayed(Runnable r, Object token, long delayMillis) { mQueue.add(new QueuedRunnable(r, token, mCurrentTimeMillis + delayMillis)); + mQueue.sort(QueuedRunnable.COMPARATOR); } @Override void removeQueuedRunnables(Object token) { - mQueue.removeIf(runnable -> runnable.token != null && runnable.token.equals(token)); + mQueue.removeIf(runnable -> runnable.token != null && runnable.token == token); } void assertSingleDelayedQueueItem(Duration expectedDelay) { @@ -114,14 +121,14 @@ class TestThreadingDomain extends ThreadingDomain { } long getNextQueueItemDelayMillis() { - assertQueueLength(1); - return mQueue.getFirst().executionTimeMillis - mCurrentTimeMillis; + assertFalse(mQueue.isEmpty()); + return mQueue.get(0).executionTimeMillis - mCurrentTimeMillis; } void executeNext() { - assertQueueLength(1); + assertFalse(mQueue.isEmpty()); + QueuedRunnable queued = mQueue.remove(0); - QueuedRunnable queued = mQueue.removeFirst(); mCurrentTimeMillis = queued.executionTimeMillis; queued.runnable.run(); } 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 c5d94875b684..c6823ebfd655 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 @@ -37,6 +37,7 @@ public final class ConversationInfoTest { private static final Uri CONTACT_URI = Uri.parse("tel:+1234567890"); private static final String PHONE_NUMBER = "+1234567890"; private static final String NOTIFICATION_CHANNEL_ID = "test : abc"; + private static final String PARENT_NOTIFICATION_CHANNEL_ID = "test"; @Test public void testBuild() { @@ -46,6 +47,8 @@ public final class ConversationInfoTest { .setContactUri(CONTACT_URI) .setContactPhoneNumber(PHONE_NUMBER) .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) + .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID) + .setLastEventTimestamp(100L) .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED | ShortcutInfo.FLAG_CACHED_NOTIFICATIONS) .setImportant(true) @@ -62,6 +65,9 @@ public final class ConversationInfoTest { assertEquals(CONTACT_URI, conversationInfo.getContactUri()); assertEquals(PHONE_NUMBER, conversationInfo.getContactPhoneNumber()); assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId()); + assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, + conversationInfo.getParentNotificationChannelId()); + assertEquals(100L, conversationInfo.getLastEventTimestamp()); assertTrue(conversationInfo.isShortcutLongLived()); assertTrue(conversationInfo.isShortcutCachedForNotification()); assertTrue(conversationInfo.isImportant()); @@ -84,6 +90,8 @@ public final class ConversationInfoTest { assertNull(conversationInfo.getContactUri()); assertNull(conversationInfo.getContactPhoneNumber()); assertNull(conversationInfo.getNotificationChannelId()); + assertNull(conversationInfo.getParentNotificationChannelId()); + assertEquals(0L, conversationInfo.getLastEventTimestamp()); assertFalse(conversationInfo.isShortcutLongLived()); assertFalse(conversationInfo.isShortcutCachedForNotification()); assertFalse(conversationInfo.isImportant()); @@ -103,6 +111,8 @@ public final class ConversationInfoTest { .setContactUri(CONTACT_URI) .setContactPhoneNumber(PHONE_NUMBER) .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) + .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID) + .setLastEventTimestamp(100L) .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) .setImportant(true) .setNotificationSilenced(true) @@ -122,6 +132,8 @@ public final class ConversationInfoTest { assertEquals(CONTACT_URI, destination.getContactUri()); assertEquals(PHONE_NUMBER, destination.getContactPhoneNumber()); assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId()); + assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId()); + assertEquals(100L, destination.getLastEventTimestamp()); assertTrue(destination.isShortcutLongLived()); assertFalse(destination.isImportant()); assertTrue(destination.isNotificationSilenced()); 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 b2f7abbf84df..f37054d269b1 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 @@ -29,6 +29,7 @@ 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.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; @@ -45,6 +46,7 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Person; import android.app.job.JobScheduler; +import android.app.people.ConversationChannel; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.prediction.AppTargetId; @@ -112,6 +114,7 @@ public final class DataManagerTest { private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123"; private static final String PHONE_NUMBER = "+1234567890"; private static final String NOTIFICATION_CHANNEL_ID = "test : sc"; + private static final String PARENT_NOTIFICATION_CHANNEL_ID = "test"; private static final long MILLIS_PER_MINUTE = 1000L * 60L; @Mock private Context mContext; @@ -133,10 +136,12 @@ public final class DataManagerTest { private ScheduledExecutorService mExecutorService; private NotificationChannel mNotificationChannel; + private NotificationChannel mParentNotificationChannel; private DataManager mDataManager; private CancellationSignal mCancellationSignal; private ShortcutChangeCallback mShortcutChangeCallback; private BroadcastReceiver mShutdownBroadcastReceiver; + private ShortcutInfo mShortcutInfo; private TestInjector mInjector; @Before @@ -157,6 +162,11 @@ public final class DataManagerTest { }).when(mPackageManagerInternal).forEachInstalledPackage(any(Consumer.class), anyInt()); addLocalServiceMock(NotificationManagerInternal.class, mNotificationManagerInternal); + mParentNotificationChannel = new NotificationChannel( + PARENT_NOTIFICATION_CHANNEL_ID, "test channel", + NotificationManager.IMPORTANCE_DEFAULT); + when(mNotificationManagerInternal.getNotificationChannel(anyString(), anyInt(), + anyString())).thenReturn(mParentNotificationChannel); when(mContext.getContentResolver()).thenReturn(mContentResolver); when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); @@ -199,6 +209,7 @@ public final class DataManagerTest { when(mStatusBarNotification.getUser()).thenReturn(UserHandle.of(USER_ID_PRIMARY)); when(mStatusBarNotification.getPostTime()).thenReturn(System.currentTimeMillis()); when(mNotification.getShortcutId()).thenReturn(TEST_SHORTCUT_ID); + when(mNotification.getChannelId()).thenReturn(PARENT_NOTIFICATION_CHANNEL_ID); mNotificationChannel = new NotificationChannel( NOTIFICATION_CHANNEL_ID, "test channel", NotificationManager.IMPORTANCE_DEFAULT); @@ -212,6 +223,13 @@ public final class DataManagerTest { when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), anyString(), anyInt(), any())).thenReturn(true); + + mShortcutInfo = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + when(mShortcutServiceInternal.getShortcuts( + anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(), + anyInt(), anyInt(), anyInt(), anyInt())) + .thenReturn(Collections.singletonList(mShortcutInfo)); verify(mShortcutServiceInternal).addShortcutChangeCallback( mShortcutChangeCallbackCaptor.capture()); mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue(); @@ -417,29 +435,28 @@ public final class DataManagerTest { List<Range<Long>> activeNotificationOpenTimeSlots = getActiveSlotsForTestShortcut( Event.NOTIFICATION_EVENT_TYPES); assertEquals(1, activeNotificationOpenTimeSlots.size()); - verify(mShortcutServiceInternal).uncacheShortcuts( - anyInt(), any(), eq(TEST_PKG_NAME), - eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY), - eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)); } @Test - public void testNotificationDismissed() { + public void testUncacheShortcutsWhenNotificationsDismissed() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); - - ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, - buildPerson()); - mDataManager.addOrUpdateConversationInfo(shortcut); - NotificationListenerService listenerService = mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); - // Post one notification. - shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); - mDataManager.addOrUpdateConversationInfo(shortcut); - listenerService.onNotificationPosted(mStatusBarNotification); + // The cached conversations are above the limit because every conversation has active + // notifications. To uncache one of them, the notifications for that conversation need to + // be dismissed. + for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) { + String shortcutId = TEST_SHORTCUT_ID + i; + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId, + buildPerson()); + shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); + mDataManager.addOrUpdateConversationInfo(shortcut); + when(mNotification.getShortcutId()).thenReturn(shortcutId); + listenerService.onNotificationPosted(mStatusBarNotification); + } - // Post another notification. + // Post another notification for the last conversation. listenerService.onNotificationPosted(mStatusBarNotification); // Removing one of the two notifications does not un-cache the shortcut. @@ -452,13 +469,12 @@ public final class DataManagerTest { listenerService.onNotificationRemoved(mStatusBarNotification, null, NotificationListenerService.REASON_CANCEL_ALL); verify(mShortcutServiceInternal).uncacheShortcuts( - anyInt(), any(), eq(TEST_PKG_NAME), - eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY), + anyInt(), any(), eq(TEST_PKG_NAME), anyList(), eq(USER_ID_PRIMARY), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)); } @Test - public void testShortcutNotUncachedIfNotificationChannelCreated() { + public void testConversationIsNotRecentIfCustomized() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, @@ -472,15 +488,12 @@ public final class DataManagerTest { shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); mDataManager.addOrUpdateConversationInfo(shortcut); + assertEquals(1, mDataManager.getRecentConversations(USER_ID_PRIMARY).size()); + listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY), mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); - listenerService.onNotificationRemoved(mStatusBarNotification, null, - NotificationListenerService.REASON_CANCEL_ALL); - verify(mShortcutServiceInternal, never()).uncacheShortcuts( - anyInt(), any(), eq(TEST_PKG_NAME), - eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY), - eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)); + assertTrue(mDataManager.getRecentConversations(USER_ID_PRIMARY).isEmpty()); } @Test @@ -561,53 +574,6 @@ public final class DataManagerTest { } @Test - public void testUncacheShortcutWhenShutdown() { - mDataManager.onUserUnlocked(USER_ID_PRIMARY); - - ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, - buildPerson()); - mDataManager.addOrUpdateConversationInfo(shortcut); - - NotificationListenerService listenerService = - mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); - - listenerService.onNotificationPosted(mStatusBarNotification); - shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); - mDataManager.addOrUpdateConversationInfo(shortcut); - - mShutdownBroadcastReceiver.onReceive(mContext, new Intent()); - verify(mShortcutServiceInternal).uncacheShortcuts( - anyInt(), any(), eq(TEST_PKG_NAME), - eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY), - eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)); - } - - @Test - public void testDoNotUncacheShortcutWhenShutdownIfNotificationChannelCreated() { - mDataManager.onUserUnlocked(USER_ID_PRIMARY); - - ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, - buildPerson()); - mDataManager.addOrUpdateConversationInfo(shortcut); - - NotificationListenerService listenerService = - mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); - - listenerService.onNotificationPosted(mStatusBarNotification); - shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); - mDataManager.addOrUpdateConversationInfo(shortcut); - - listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY), - mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); - - mShutdownBroadcastReceiver.onReceive(mContext, new Intent()); - verify(mShortcutServiceInternal, never()).uncacheShortcuts( - anyInt(), any(), eq(TEST_PKG_NAME), - eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY), - eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)); - } - - @Test public void testShortcutAddedOrUpdated() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); @@ -623,17 +589,19 @@ public final class DataManagerTest { } @Test - public void testShortcutDeleted() { + public void testShortcutsDeleted() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); ShortcutInfo shortcut1 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc1", buildPerson()); ShortcutInfo shortcut2 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc2", buildPerson()); + ShortcutInfo shortcut3 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc3", + buildPerson()); mShortcutChangeCallback.onShortcutsAddedOrUpdated(TEST_PKG_NAME, - Arrays.asList(shortcut1, shortcut2), UserHandle.of(USER_ID_PRIMARY)); + Arrays.asList(shortcut1, shortcut2, shortcut3), UserHandle.of(USER_ID_PRIMARY)); mShortcutChangeCallback.onShortcutsRemoved(TEST_PKG_NAME, - Collections.singletonList(shortcut1), UserHandle.of(USER_ID_PRIMARY)); + List.of(shortcut1, shortcut3), UserHandle.of(USER_ID_PRIMARY)); List<ConversationInfo> conversations = getConversationsInPrimary(); @@ -641,7 +609,7 @@ public final class DataManagerTest { assertEquals("sc2", conversations.get(0).getShortcutId()); verify(mNotificationManagerInternal) - .onConversationRemoved(TEST_PKG_NAME, TEST_PKG_UID, "sc1"); + .onConversationRemoved(TEST_PKG_NAME, TEST_PKG_UID, Set.of("sc1", "sc3")); } @Test @@ -767,20 +735,57 @@ public final class DataManagerTest { } @Test - public void testPruneInactiveCachedShortcuts() { + public void testDoNotUncacheShortcutWithActiveNotifications() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); - ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, - buildPerson()); - shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); - mDataManager.addOrUpdateConversationInfo(shortcut); + for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) { + String shortcutId = TEST_SHORTCUT_ID + i; + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId, + buildPerson()); + shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); + mDataManager.addOrUpdateConversationInfo(shortcut); + when(mNotification.getShortcutId()).thenReturn(shortcutId); + listenerService.onNotificationPosted(mStatusBarNotification); + } mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal); + verify(mShortcutServiceInternal, never()).uncacheShortcuts( + anyInt(), anyString(), anyString(), anyList(), anyInt(), anyInt()); + } + + @Test + public void testUncacheOldestCachedShortcut() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + + for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) { + String shortcutId = TEST_SHORTCUT_ID + i; + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId, + buildPerson()); + shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); + mDataManager.addOrUpdateConversationInfo(shortcut); + when(mNotification.getShortcutId()).thenReturn(shortcutId); + when(mStatusBarNotification.getPostTime()).thenReturn(100L + i); + listenerService.onNotificationPosted(mStatusBarNotification); + listenerService.onNotificationRemoved(mStatusBarNotification, null, + NotificationListenerService.REASON_CANCEL); + } + + // Only the shortcut #0 is uncached, all the others are not. verify(mShortcutServiceInternal).uncacheShortcuts( anyInt(), any(), eq(TEST_PKG_NAME), - eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY), + eq(Collections.singletonList(TEST_SHORTCUT_ID + 0)), eq(USER_ID_PRIMARY), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)); + for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) { + verify(mShortcutServiceInternal, never()).uncacheShortcuts( + anyInt(), anyString(), anyString(), + eq(Collections.singletonList(TEST_SHORTCUT_ID + i)), anyInt(), + eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)); + } } @Test @@ -810,6 +815,148 @@ public final class DataManagerTest { assertEquals(conversationInfo.getShortcutId(), TEST_SHORTCUT_ID); } + @Test + public void testGetRecentConversations() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); + mDataManager.addOrUpdateConversationInfo(shortcut); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationPosted(mStatusBarNotification); + + List<ConversationChannel> result = mDataManager.getRecentConversations(USER_ID_PRIMARY); + assertEquals(1, result.size()); + assertEquals(shortcut.getId(), result.get(0).getShortcutInfo().getId()); + assertEquals(mParentNotificationChannel.getId(), + result.get(0).getParentNotificationChannel().getId()); + assertEquals(mStatusBarNotification.getPostTime(), result.get(0).getLastEventTimestamp()); + assertTrue(result.get(0).hasActiveNotifications()); + } + + @Test + public void testGetLastInteraction() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.addOrUpdateConversationInfo(shortcut); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationPosted(mStatusBarNotification); + + assertEquals(mStatusBarNotification.getPostTime(), + mDataManager.getLastInteraction(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID)); + assertEquals(0L, + mDataManager.getLastInteraction("not_test_pkg", USER_ID_PRIMARY, TEST_SHORTCUT_ID)); + assertEquals(0L, + mDataManager.getLastInteraction(TEST_PKG_NAME, USER_ID_PRIMARY_MANAGED, + TEST_SHORTCUT_ID)); + assertEquals(0L, + mDataManager.getLastInteraction(TEST_PKG_NAME, USER_ID_SECONDARY, + TEST_SHORTCUT_ID)); + } + + @Test + public void testNonCachedShortcutNotInRecentList() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY_MANAGED, + TEST_SHORTCUT_ID, buildPerson()); + mDataManager.addOrUpdateConversationInfo(shortcut); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationPosted(mStatusBarNotification); + + List<ConversationChannel> result = mDataManager.getRecentConversations(USER_ID_PRIMARY); + assertTrue(result.isEmpty()); + } + + @Test + public void testCustomizedConversationNotInRecentList() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); + mDataManager.addOrUpdateConversationInfo(shortcut); + + // Post a notification and customize the notification settings. + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationPosted(mStatusBarNotification); + listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY), + mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + + List<ConversationChannel> result = mDataManager.getRecentConversations(USER_ID_PRIMARY); + assertTrue(result.isEmpty()); + } + + @Test + public void testRemoveRecentConversation() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); + mDataManager.addOrUpdateConversationInfo(shortcut); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationPosted(mStatusBarNotification); + listenerService.onNotificationRemoved(mStatusBarNotification, null, + NotificationListenerService.REASON_CANCEL); + mDataManager.removeRecentConversation(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + USER_ID_PRIMARY); + + verify(mShortcutServiceInternal).uncacheShortcuts( + anyInt(), any(), eq(TEST_PKG_NAME), eq(Collections.singletonList(TEST_SHORTCUT_ID)), + eq(USER_ID_PRIMARY), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)); + } + + @Test + public void testRemoveAllRecentConversations() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut1 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "1", + buildPerson()); + shortcut1.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); + mDataManager.addOrUpdateConversationInfo(shortcut1); + + ShortcutInfo shortcut2 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "2", + buildPerson()); + shortcut2.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); + mDataManager.addOrUpdateConversationInfo(shortcut2); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + + // Post a notification and then dismiss it for conversation #1. + when(mNotification.getShortcutId()).thenReturn("1"); + listenerService.onNotificationPosted(mStatusBarNotification); + listenerService.onNotificationRemoved(mStatusBarNotification, null, + NotificationListenerService.REASON_CANCEL); + + // Post a notification for conversation #2, but don't dismiss it. Its shortcut won't be + // uncached when removeAllRecentConversations() is called. + when(mNotification.getShortcutId()).thenReturn("2"); + listenerService.onNotificationPosted(mStatusBarNotification); + + mDataManager.removeAllRecentConversations(USER_ID_PRIMARY); + + verify(mShortcutServiceInternal).uncacheShortcuts( + anyInt(), any(), eq(TEST_PKG_NAME), eq(Collections.singletonList("1")), + eq(USER_ID_PRIMARY), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)); + verify(mShortcutServiceInternal, never()).uncacheShortcuts( + anyInt(), any(), eq(TEST_PKG_NAME), eq(Collections.singletonList("2")), + eq(USER_ID_PRIMARY), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)); + } + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { LocalServices.removeServiceForTest(clazz); LocalServices.addService(clazz, mock); diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 419fb14df340..6febae00f0fb 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -80,6 +80,7 @@ import com.android.server.SystemService; import com.android.server.lights.LightsManager; import com.android.server.policy.WindowManagerPolicy; import com.android.server.power.PowerManagerService.BatteryReceiver; +import com.android.server.power.PowerManagerService.BinderService; import com.android.server.power.PowerManagerService.Injector; import com.android.server.power.PowerManagerService.NativeWrapper; import com.android.server.power.PowerManagerService.UserSwitchedReceiver; @@ -179,6 +180,7 @@ public class PowerManagerServiceTest { when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false); when(mDisplayManagerInternalMock.requestPowerState(any(), anyBoolean())).thenReturn(true); when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn(""); + when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true); mDisplayPowerRequest = new DisplayPowerRequest(); addLocalServiceMock(LightsManager.class, mLightsManagerMock); @@ -983,6 +985,74 @@ public class PowerManagerServiceTest { } @Test + public void testIsAmbientDisplaySuppressedForTokenByApp_ambientDisplayUnavailable() + throws Exception { + createService(); + when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false); + + BinderService service = mService.getBinderServiceInstance(); + assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid())) + .isFalse(); + } + + @Test + public void testIsAmbientDisplaySuppressedForTokenByApp_default() + throws Exception { + createService(); + + BinderService service = mService.getBinderServiceInstance(); + assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid())) + .isFalse(); + } + + @Test + public void testIsAmbientDisplaySuppressedForTokenByApp_suppressedByCallingApp() + throws Exception { + createService(); + BinderService service = mService.getBinderServiceInstance(); + service.suppressAmbientDisplay("test", true); + + assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid())) + .isTrue(); + // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app. + assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", /* appUid= */ 123)) + .isFalse(); + } + + @Test + public void testIsAmbientDisplaySuppressedForTokenByApp_notSuppressedByCallingApp() + throws Exception { + createService(); + BinderService service = mService.getBinderServiceInstance(); + service.suppressAmbientDisplay("test", false); + + assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid())) + .isFalse(); + // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app. + assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", /* appUid= */ 123)) + .isFalse(); + } + + @Test + public void testIsAmbientDisplaySuppressedForTokenByApp_multipleTokensSuppressedByCallingApp() + throws Exception { + createService(); + BinderService service = mService.getBinderServiceInstance(); + service.suppressAmbientDisplay("test1", true); + service.suppressAmbientDisplay("test2", true); + + assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test1", Binder.getCallingUid())) + .isTrue(); + assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test2", Binder.getCallingUid())) + .isTrue(); + // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app. + assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test1", /* appUid= */ 123)) + .isFalse(); + assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test2", /* appUid= */ 123)) + .isFalse(); + } + + @Test public void testSetPowerBoost_redirectsCallToNativeWrapper() { createService(); mService.systemReady(null); diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java index d7ed96fd5833..54b5bee9a6ab 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java @@ -16,22 +16,23 @@ package com.android.server.timezonedetector; -import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED; -import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE; -import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED; -import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import android.app.timezonedetector.TimeZoneCapabilities; +import android.app.time.TimeZoneCapabilities; +import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneConfiguration; import org.junit.Test; /** - * Tests for {@link ConfigurationInternal} and the {@link TimeZoneCapabilities} and - * {@link android.app.timezonedetector.TimeZoneConfiguration} that can be generated from it. + * Tests for {@link ConfigurationInternal} and the {@link TimeZoneCapabilitiesAndConfig}. */ public class ConfigurationInternalTest { @@ -59,13 +60,20 @@ public class ConfigurationInternalTest { assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior()); assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior()); - TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities(); - assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled()); - assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled()); - assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeZone()); - assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration()); - assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled()); - assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled()); + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + autoOnConfig.createCapabilitiesAndConfig(); + + TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + assertEquals(CAPABILITY_POSSESSED, + capabilities.getConfigureAutoDetectionEnabledCapability()); + assertEquals(CAPABILITY_POSSESSED, + capabilities.getConfigureGeoDetectionEnabledCapability()); + assertEquals(CAPABILITY_NOT_APPLICABLE, + capabilities.getSuggestManualTimeZoneCapability()); + + TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + assertTrue(configuration.isAutoDetectionEnabled()); + assertTrue(configuration.isGeoDetectionEnabled()); } { @@ -77,13 +85,20 @@ public class ConfigurationInternalTest { assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior()); assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior()); - TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities(); - assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled()); - assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled()); - assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone()); - assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration()); - assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled()); - assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled()); + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + autoOffConfig.createCapabilitiesAndConfig(); + + TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + assertEquals(CAPABILITY_POSSESSED, + capabilities.getConfigureAutoDetectionEnabledCapability()); + assertEquals(CAPABILITY_POSSESSED, + capabilities.getConfigureGeoDetectionEnabledCapability()); + assertEquals(CAPABILITY_POSSESSED, + capabilities.getSuggestManualTimeZoneCapability()); + + TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + assertFalse(configuration.isAutoDetectionEnabled()); + assertTrue(configuration.isGeoDetectionEnabled()); } } @@ -106,13 +121,20 @@ public class ConfigurationInternalTest { assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior()); assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior()); - TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities(); - assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled()); - assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled()); - assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone()); - assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration()); - assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled()); - assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled()); + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + autoOnConfig.createCapabilitiesAndConfig(); + + TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + assertEquals(CAPABILITY_NOT_ALLOWED, + capabilities.getConfigureAutoDetectionEnabledCapability()); + assertEquals(CAPABILITY_NOT_ALLOWED, + capabilities.getConfigureGeoDetectionEnabledCapability()); + assertEquals(CAPABILITY_NOT_ALLOWED, + capabilities.getSuggestManualTimeZoneCapability()); + + TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + assertTrue(configuration.isAutoDetectionEnabled()); + assertTrue(configuration.isGeoDetectionEnabled()); } { @@ -124,13 +146,20 @@ public class ConfigurationInternalTest { assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior()); assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior()); - TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities(); - assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled()); - assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled()); - assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone()); - assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration()); - assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled()); - assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled()); + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + autoOffConfig.createCapabilitiesAndConfig(); + + TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + assertEquals(CAPABILITY_NOT_ALLOWED, + capabilities.getConfigureAutoDetectionEnabledCapability()); + assertEquals(CAPABILITY_NOT_ALLOWED, + capabilities.getConfigureGeoDetectionEnabledCapability()); + assertEquals(CAPABILITY_NOT_ALLOWED, + capabilities.getSuggestManualTimeZoneCapability()); + + TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + assertFalse(configuration.isAutoDetectionEnabled()); + assertTrue(configuration.isGeoDetectionEnabled()); } } @@ -153,13 +182,19 @@ public class ConfigurationInternalTest { assertFalse(autoOnConfig.getAutoDetectionEnabledBehavior()); assertFalse(autoOnConfig.getGeoDetectionEnabledBehavior()); - TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities(); - assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled()); - assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled()); - assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone()); - assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration()); - assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled()); - assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled()); + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + autoOnConfig.createCapabilitiesAndConfig(); + + TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + assertEquals(CAPABILITY_NOT_SUPPORTED, + capabilities.getConfigureAutoDetectionEnabledCapability()); + assertEquals(CAPABILITY_NOT_SUPPORTED, + capabilities.getConfigureGeoDetectionEnabledCapability()); + assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability()); + + TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + assertTrue(configuration.isAutoDetectionEnabled()); + assertTrue(configuration.isGeoDetectionEnabled()); } { ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig) @@ -170,13 +205,19 @@ public class ConfigurationInternalTest { assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior()); assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior()); - TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities(); - assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled()); - assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled()); - assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone()); - assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration()); - assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled()); - assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled()); + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + autoOffConfig.createCapabilitiesAndConfig(); + + TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + assertEquals(CAPABILITY_NOT_SUPPORTED, + capabilities.getConfigureAutoDetectionEnabledCapability()); + assertEquals(CAPABILITY_NOT_SUPPORTED, + capabilities.getConfigureGeoDetectionEnabledCapability()); + assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability()); + + TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + assertFalse(configuration.isAutoDetectionEnabled()); + assertTrue(configuration.isGeoDetectionEnabled()); } } } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java index 4ef20829f2dc..bad380acf4b3 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java @@ -22,10 +22,11 @@ import static org.junit.Assert.fail; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.time.TimeZoneCapabilities; +import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneConfiguration; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; -import android.app.timezonedetector.TimeZoneCapabilities; -import android.app.timezonedetector.TimeZoneConfiguration; import android.util.IndentingPrintWriter; import java.util.ArrayList; @@ -67,20 +68,25 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { } @Override - public boolean updateConfiguration(@NonNull TimeZoneConfiguration requestedChanges) { + public boolean updateConfiguration( + @UserIdInt int userID, @NonNull TimeZoneConfiguration requestedChanges) { assertNotNull(mConfigurationInternal); assertNotNull(requestedChanges); // Simulate the real strategy's behavior: the new configuration will be updated to be the // old configuration merged with the new if the user has the capability to up the settings. // Then, if the configuration changed, the change listener is invoked. - TimeZoneCapabilities capabilities = mConfigurationInternal.createCapabilities(); - TimeZoneConfiguration newConfiguration = capabilities.applyUpdate(requestedChanges); + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + mConfigurationInternal.createCapabilitiesAndConfig(); + TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); + TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration(); + TimeZoneConfiguration newConfiguration = + capabilities.tryApplyConfigChanges(configuration, requestedChanges); if (newConfiguration == null) { return false; } - if (!newConfiguration.equals(capabilities.getConfiguration())) { + if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) { mConfigurationInternal = mConfigurationInternal.merge(newConfiguration); // Note: Unlike the real strategy, the listeners is invoked synchronously. diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java index 27b04b6ab17d..cb27657f8364 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java @@ -31,10 +31,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import android.app.timezonedetector.ITimeZoneConfigurationListener; +import android.app.time.ITimeZoneDetectorListener; +import android.app.time.TimeZoneConfiguration; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; -import android.app.timezonedetector.TimeZoneConfiguration; import android.content.Context; import android.content.pm.PackageManager; import android.os.HandlerThread; @@ -91,85 +91,85 @@ public class TimeZoneDetectorServiceTest { } @Test(expected = SecurityException.class) - public void testGetCapabilities_withoutPermission() { + public void testGetCapabilitiesAndConfig_withoutPermission() { doThrow(new SecurityException("Mock")) .when(mMockContext).enforceCallingPermission(anyString(), any()); try { - mTimeZoneDetectorService.getCapabilities(); + mTimeZoneDetectorService.getCapabilitiesAndConfig(); fail(); } finally { verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.WRITE_SECURE_SETTINGS), + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString()); } } @Test - public void testGetCapabilities() { + public void testGetCapabilitiesAndConfig() { doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); ConfigurationInternal configuration = createConfigurationInternal(true /* autoDetectionEnabled*/); mFakeTimeZoneDetectorStrategy.initializeConfiguration(configuration); - assertEquals(configuration.createCapabilities(), - mTimeZoneDetectorService.getCapabilities()); + assertEquals(configuration.createCapabilitiesAndConfig(), + mTimeZoneDetectorService.getCapabilitiesAndConfig()); verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.WRITE_SECURE_SETTINGS), + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString()); } @Test(expected = SecurityException.class) - public void testAddConfigurationListener_withoutPermission() { + public void testAddListener_withoutPermission() { doThrow(new SecurityException("Mock")) .when(mMockContext).enforceCallingPermission(anyString(), any()); - ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class); + ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class); try { - mTimeZoneDetectorService.addConfigurationListener(mockListener); + mTimeZoneDetectorService.addListener(mockListener); fail(); } finally { verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.WRITE_SECURE_SETTINGS), + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString()); } } @Test(expected = SecurityException.class) - public void testRemoveConfigurationListener_withoutPermission() { + public void testRemoveListener_withoutPermission() { doThrow(new SecurityException("Mock")) .when(mMockContext).enforceCallingPermission(anyString(), any()); - ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class); + ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class); try { - mTimeZoneDetectorService.removeConfigurationListener(mockListener); + mTimeZoneDetectorService.removeListener(mockListener); fail("Expected a SecurityException"); } finally { verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.WRITE_SECURE_SETTINGS), + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString()); } } @Test - public void testConfigurationChangeListenerRegistrationAndCallbacks() throws Exception { + public void testListenerRegistrationAndCallbacks() throws Exception { ConfigurationInternal initialConfiguration = createConfigurationInternal(false /* autoDetectionEnabled */); mFakeTimeZoneDetectorStrategy.initializeConfiguration(initialConfiguration); IBinder mockListenerBinder = mock(IBinder.class); - ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class); + ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class); { doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); when(mockListener.asBinder()).thenReturn(mockListenerBinder); - mTimeZoneDetectorService.addConfigurationListener(mockListener); + mTimeZoneDetectorService.addListener(mockListener); verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.WRITE_SECURE_SETTINGS), + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString()); verify(mockListener).asBinder(); verify(mockListenerBinder).linkToDeath(any(), anyInt()); @@ -186,7 +186,7 @@ public class TimeZoneDetectorServiceTest { mTimeZoneDetectorService.updateConfiguration(autoDetectEnabledConfiguration); verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.WRITE_SECURE_SETTINGS), + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString()); verify(mockListener).onChange(); verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext); @@ -200,10 +200,10 @@ public class TimeZoneDetectorServiceTest { // Now remove the listener, change the config again, and verify the listener is not // called. - mTimeZoneDetectorService.removeConfigurationListener(mockListener); + mTimeZoneDetectorService.removeListener(mockListener); verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.WRITE_SECURE_SETTINGS), + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString()); verify(mockListener).asBinder(); verify(mockListenerBinder).unlinkToDeath(any(), eq(0)); @@ -219,7 +219,7 @@ public class TimeZoneDetectorServiceTest { mTimeZoneDetectorService.updateConfiguration(autoDetectDisabledConfiguration); verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.WRITE_SECURE_SETTINGS), + eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString()); verify(mockListener, never()).onChange(); verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext); @@ -354,7 +354,7 @@ public class TimeZoneDetectorServiceTest { } private static TimeZoneConfiguration createTimeZoneConfiguration(boolean autoDetectionEnabled) { - return new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID) + return new TimeZoneConfiguration.Builder() .setAutoDetectionEnabled(autoDetectionEnabled) .build(); } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index 1cdf19319209..296aa73f9c45 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -39,11 +39,11 @@ import static org.junit.Assert.fail; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.time.TimeZoneConfiguration; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType; import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality; -import android.app.timezonedetector.TimeZoneConfiguration; import android.util.IndentingPrintWriter; import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion; @@ -186,26 +186,27 @@ public class TimeZoneDetectorStrategyImplTest { Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED); // Set the configuration with auto detection enabled. - script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */); + script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */); // Nothing should have happened: it was initialized in this state. script.verifyConfigurationNotChanged(); // Update the configuration with auto detection disabled. - script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */); + script.simulateUpdateConfiguration( + USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */); // The settings should have been changed and the StrategyListener onChange() called. script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED); // Update the configuration with auto detection enabled. - script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */); + script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */); // The settings should have been changed and the StrategyListener onChange() called. script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED); // Update the configuration to enable geolocation time zone detection. script.simulateUpdateConfiguration( - CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */); + USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */); // The settings should have been changed and the StrategyListener onChange() called. script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED); @@ -216,20 +217,22 @@ public class TimeZoneDetectorStrategyImplTest { Script script = new Script().initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED); // Try to update the configuration with auto detection disabled. - script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, false /* expectedResult */); + script.simulateUpdateConfiguration( + USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */); // The settings should not have been changed: user shouldn't have the capabilities. script.verifyConfigurationNotChanged(); // Update the configuration with auto detection enabled. - script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, false /* expectedResult */); + script.simulateUpdateConfiguration( + USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */); // The settings should not have been changed: user shouldn't have the capabilities. script.verifyConfigurationNotChanged(); // Try to update the configuration to enable geolocation time zone detection. script.simulateUpdateConfiguration( - CONFIG_GEO_DETECTION_ENABLED, false /* expectedResult */); + USER_ID, CONFIG_GEO_DETECTION_ENABLED, false /* expectedResult */); // The settings should not have been changed: user shouldn't have the capabilities. script.verifyConfigurationNotChanged(); @@ -240,13 +243,15 @@ public class TimeZoneDetectorStrategyImplTest { Script script = new Script().initializeConfig(CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED); // Try to update the configuration with auto detection disabled. - script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, false /* expectedResult */); + script.simulateUpdateConfiguration( + USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */); // The settings should not have been changed: user shouldn't have the capabilities. script.verifyConfigurationNotChanged(); // Update the configuration with auto detection enabled. - script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, false /* expectedResult */); + script.simulateUpdateConfiguration( + USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */); // The settings should not have been changed: user shouldn't have the capabilities. script.verifyConfigurationNotChanged(); @@ -389,7 +394,8 @@ public class TimeZoneDetectorStrategyImplTest { mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); // Toggling the time zone setting on should cause the device setting to be set. - script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */); + script.simulateUpdateConfiguration( + USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */); // When time zone detection is already enabled the suggestion (if it scores highly // enough) should be set immediately. @@ -406,7 +412,8 @@ public class TimeZoneDetectorStrategyImplTest { mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); // Toggling the time zone setting should off should do nothing. - script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */) + script.simulateUpdateConfiguration( + USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */) .verifyTimeZoneNotChanged(); // Assert internal service state. @@ -588,18 +595,20 @@ public class TimeZoneDetectorStrategyImplTest { // Toggling time zone detection should set the device time zone only if the current setting // value is different from the most recent telephony suggestion. - script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */) + script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */) .verifyTimeZoneNotChanged() - .simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */) + .simulateUpdateConfiguration( + USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */) .verifyTimeZoneNotChanged(); // Simulate a user turning auto detection off, a new suggestion being made while auto // detection is off, and the user turning it on again. - script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */) + script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */) .simulateTelephonyTimeZoneSuggestion(newYorkSuggestion) .verifyTimeZoneNotChanged(); // Latest suggestion should be used. - script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */) + script.simulateUpdateConfiguration( + USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */) .verifyTimeZoneChangedAndReset(newYorkSuggestion); } @@ -784,7 +793,7 @@ public class TimeZoneDetectorStrategyImplTest { assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion()); // Turn off geo detection and verify the latest suggestion is cleared. - script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true) + script.simulateUpdateConfiguration(USER_ID, CONFIG_GEO_DETECTION_DISABLED, true) .verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED); // Assert internal service state. @@ -824,19 +833,21 @@ public class TimeZoneDetectorStrategyImplTest { // Toggling the time zone detection enabled setting on should cause the device setting to be // set from the telephony signal, as we've started with geolocation time zone detection // disabled. - script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */) + script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */) .verifyTimeZoneChangedAndReset(telephonySuggestion); // Changing the detection to enable geo detection won't cause the device tz setting to // change because the geo suggestion is empty. - script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */) + script.simulateUpdateConfiguration( + USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */) .verifyTimeZoneNotChanged() .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion) .verifyTimeZoneChangedAndReset(geolocationSuggestion.getZoneIds().get(0)); // Changing the detection to disable geo detection should cause the device tz setting to // change to the telephony suggestion. - script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */) + script.simulateUpdateConfiguration( + USER_ID, CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */) .verifyTimeZoneChangedAndReset(telephonySuggestion); assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion()); @@ -898,7 +909,7 @@ public class TimeZoneDetectorStrategyImplTest { private static TimeZoneConfiguration createConfig( @Nullable Boolean autoDetection, @Nullable Boolean geoDetection) { - TimeZoneConfiguration.Builder builder = new TimeZoneConfiguration.Builder(USER_ID); + TimeZoneConfiguration.Builder builder = new TimeZoneConfiguration.Builder(); if (autoDetection != null) { builder.setAutoDetectionEnabled(autoDetection); } @@ -957,9 +968,10 @@ public class TimeZoneDetectorStrategyImplTest { } @Override - public void storeConfiguration(TimeZoneConfiguration newConfiguration) { + public void storeConfiguration( + @UserIdInt int userId, TimeZoneConfiguration newConfiguration) { ConfigurationInternal oldConfiguration = mConfigurationInternal.getLatest(); - if (newConfiguration.getUserId() != oldConfiguration.getUserId()) { + if (userId != oldConfiguration.getUserId()) { fail("FakeCallback does not support multiple users"); } @@ -1014,9 +1026,9 @@ public class TimeZoneDetectorStrategyImplTest { * the return value. */ Script simulateUpdateConfiguration( - TimeZoneConfiguration configuration, boolean expectedResult) { + int userId, TimeZoneConfiguration configuration, boolean expectedResult) { assertEquals(expectedResult, - mTimeZoneDetectorStrategy.updateConfiguration(configuration)); + mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration)); return this; } diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 2c645268e190..0c6d6388b458 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -84,6 +84,7 @@ import com.android.server.SystemService; import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -703,6 +704,7 @@ public class AppStandbyControllerTests { assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); } + @Ignore @Test public void testPredictionTimedOut() throws Exception { // Set it to timeout or usage, so that prediction can override it diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java index a2d987fb0a8d..f6d6624d7e1c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java @@ -44,15 +44,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.internal.matchers.Not; import java.io.File; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import java.util.Set; @RunWith(AndroidJUnit4.class) public class NotificationHistoryDatabaseTest extends UiServiceTestCase { @@ -309,22 +307,22 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { public void testRemoveConversationRunnable() throws Exception { NotificationHistory nh = mock(NotificationHistory.class); NotificationHistoryDatabase.RemoveConversationRunnable rcr = - mDataBase.new RemoveConversationRunnable("pkg", "convo"); + mDataBase.new RemoveConversationRunnable("pkg", Set.of("convo", "another")); rcr.setNotificationHistory(nh); AtomicFile af = mock(AtomicFile.class); when(af.getBaseFile()).thenReturn(new File(mRootDir, "af")); mDataBase.mHistoryFiles.addLast(af); - when(nh.removeConversationFromWrite("pkg", "convo")).thenReturn(true); + when(nh.removeConversationsFromWrite("pkg", Set.of("convo", "another"))).thenReturn(true); mDataBase.mBuffer = mock(NotificationHistory.class); rcr.run(); - verify(mDataBase.mBuffer).removeConversationFromWrite("pkg", "convo"); + verify(mDataBase.mBuffer).removeConversationsFromWrite("pkg",Set.of("convo", "another")); verify(af).openRead(); - verify(nh).removeConversationFromWrite("pkg", "convo"); + verify(nh).removeConversationsFromWrite("pkg",Set.of("convo", "another")); verify(af).startWrite(); } @@ -332,22 +330,22 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { public void testRemoveConversationRunnable_noChanges() throws Exception { NotificationHistory nh = mock(NotificationHistory.class); NotificationHistoryDatabase.RemoveConversationRunnable rcr = - mDataBase.new RemoveConversationRunnable("pkg", "convo"); + mDataBase.new RemoveConversationRunnable("pkg", Set.of("convo")); rcr.setNotificationHistory(nh); AtomicFile af = mock(AtomicFile.class); when(af.getBaseFile()).thenReturn(new File(mRootDir, "af")); mDataBase.mHistoryFiles.addLast(af); - when(nh.removeConversationFromWrite("pkg", "convo")).thenReturn(false); + when(nh.removeConversationsFromWrite("pkg", Set.of("convo"))).thenReturn(false); mDataBase.mBuffer = mock(NotificationHistory.class); rcr.run(); - verify(mDataBase.mBuffer).removeConversationFromWrite("pkg", "convo"); + verify(mDataBase.mBuffer).removeConversationsFromWrite("pkg", Set.of("convo")); verify(af).openRead(); - verify(nh).removeConversationFromWrite("pkg", "convo"); + verify(nh).removeConversationsFromWrite("pkg", Set.of("convo")); verify(af, never()).startWrite(); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java index 2341c10a9c91..a0293b7ad12a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java @@ -47,6 +47,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; +import java.util.Set; @RunWith(AndroidJUnit4.class) @@ -365,15 +366,15 @@ public class NotificationHistoryManagerTest extends UiServiceTestCase { @Test public void testDeleteConversation_userUnlocked() { String pkg = "pkg"; - String convo = "convo"; + Set<String> convos = Set.of("convo", "another"); NotificationHistoryDatabase userHistory = mock(NotificationHistoryDatabase.class); mHistoryManager.onUserUnlocked(USER_SYSTEM); mHistoryManager.replaceNotificationHistoryDatabase(USER_SYSTEM, userHistory); - mHistoryManager.deleteConversation(pkg, 1, convo); + mHistoryManager.deleteConversations(pkg, 1, convos); - verify(userHistory, times(1)).deleteConversation(pkg, convo); + verify(userHistory, times(1)).deleteConversations(pkg, convos); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 0be1bf3fe5c2..3e779a9b2435 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -127,6 +127,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ThreadLocalRandom; @SmallTest @@ -3502,8 +3503,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testDeleteConversation() { + public void testDeleteConversations() { String convoId = "convo"; + String convoIdC = "convoC"; NotificationChannel messages = new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT); mHelper.createNotificationChannel(PKG_O, UID_O, messages, true, false); @@ -3526,10 +3528,16 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel2.setConversationId(calls.getId(), convoId); mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false); + NotificationChannel channel3 = + new NotificationChannel("C person msgs", "msgs from C", IMPORTANCE_DEFAULT); + channel3.setConversationId(messages.getId(), convoIdC); + mHelper.createNotificationChannel(PKG_O, UID_O, channel3, true, false); + assertEquals(channel, mHelper.getNotificationChannel(PKG_O, UID_O, channel.getId(), false)); assertEquals(channel2, mHelper.getNotificationChannel(PKG_O, UID_O, channel2.getId(), false)); - assertEquals(2, mHelper.deleteConversation(PKG_O, UID_O, convoId).size()); + List<String> deleted = mHelper.deleteConversations(PKG_O, UID_O, Set.of(convoId, convoIdC)); + assertEquals(3, deleted.size()); assertEquals(messages, mHelper.getNotificationChannel(PKG_O, UID_O, messages.getId(), false)); @@ -3542,7 +3550,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(channel2, mHelper.getNotificationChannel(PKG_O, UID_O, channel2.getId(), true)); - assertEquals(7, mLogger.getCalls().size()); + assertEquals(9, mLogger.getCalls().size()); assertEquals( NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_CREATED, mLogger.get(0).event); // Channel messages @@ -3563,12 +3571,20 @@ public class PreferencesHelperTest extends UiServiceTestCase { mLogger.get(4).event); // Channel channel2 - Conversation A person calls assertEquals( NotificationChannelLogger.NotificationChannelEvent + .NOTIFICATION_CHANNEL_CONVERSATION_CREATED, + mLogger.get(5).event); // Channel channel3 - Conversation C person msgs + assertEquals( + NotificationChannelLogger.NotificationChannelEvent + .NOTIFICATION_CHANNEL_CONVERSATION_DELETED, + mLogger.get(6).event); // Delete Channel channel - Conversation A person msgs + assertEquals( + NotificationChannelLogger.NotificationChannelEvent .NOTIFICATION_CHANNEL_CONVERSATION_DELETED, - mLogger.get(5).event); // Delete Channel channel - Conversation A person msgs + mLogger.get(7).event); // Delete Channel channel2 - Conversation A person calls assertEquals( NotificationChannelLogger.NotificationChannelEvent .NOTIFICATION_CHANNEL_CONVERSATION_DELETED, - mLogger.get(6).event); // Delete Channel channel2 - Conversation A person calls + mLogger.get(8).event); // Delete Channel channel3 - Conversation C person msgs } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index f10cab87a4fe..dd85484605d1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1126,7 +1126,7 @@ public class ActivityRecordTests extends WindowTestsBase { // Verify the stack-top activity is occluded keyguard. assertEquals(topActivity, mStack.topRunningActivity()); - assertTrue(mStack.topActivityOccludesKeyguard()); + assertTrue(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY)); // Finish the top activity topActivity.setState(PAUSED, "true"); @@ -1135,7 +1135,7 @@ public class ActivityRecordTests extends WindowTestsBase { // Verify new top activity does not occlude keyguard. assertEquals(mActivity, mStack.topRunningActivity()); - assertFalse(mStack.topActivityOccludesKeyguard()); + assertFalse(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY)); } /** @@ -1522,7 +1522,7 @@ public class ActivityRecordTests extends WindowTestsBase { try { // Return error to skip unnecessary operation. doReturn(WindowManagerGlobal.ADD_STARTING_NOT_NEEDED).when(session).addToDisplay( - any() /* window */, anyInt() /* seq */, any() /* attrs */, + any() /* window */, any() /* attrs */, anyInt() /* viewVisibility */, anyInt() /* displayId */, any() /* outFrame */, any() /* outContentInsets */, any() /* outStableInsets */, any() /* outDisplayCutout */, any() /* outInputChannel */, diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 633d216b4327..4a9046624779 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1349,6 +1349,27 @@ public class DisplayContentTests extends WindowTestsBase { } @Test + public void testNoFixedRotationOnResumedScheduledApp() { + unblockDisplayRotation(mDisplayContent); + final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build(); + app.setVisible(false); + app.setState(Task.ActivityState.RESUMED, "test"); + mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN, + false /* alwaysKeepCurrent */); + mDisplayContent.mOpeningApps.add(app); + final int newOrientation = getRotatedOrientation(mDisplayContent); + app.setRequestedOrientation(newOrientation); + + // The condition should reject using fixed rotation because the resumed client in real case + // might get display info immediately. And the fixed rotation adjustments haven't arrived + // client side so the info may be inconsistent with the requested orientation. + verify(mDisplayContent).handleTopActivityLaunchingInDifferentOrientation(eq(app), + eq(true) /* checkOpening */); + assertFalse(app.isFixedRotationTransforming()); + assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp()); + } + + @Test public void testRecentsNotRotatingWithFixedRotation() { unblockDisplayRotation(mDisplayContent); final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java index ca739c0dd389..91cfd4e6a89d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java @@ -56,4 +56,12 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { mImeProvider.scheduleShowImePostLayout(appWin); assertTrue(mImeProvider.isImeTargetFromDisplayContentAndImeSame()); } + + @Test + public void testInputMethodInputTargetCanShowIme() { + WindowState target = createWindow(null, TYPE_APPLICATION, "app"); + mDisplayContent.mInputMethodTarget = target; + mImeProvider.scheduleShowImePostLayout(target); + assertTrue(mImeProvider.isImeTargetFromDisplayContentAndImeSame()); + } } 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 58d994c6cae3..f4f172d8e5b5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -44,7 +44,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -292,8 +291,7 @@ public class RecentTasksTest extends WindowTestsBase { mRecentTasks.add(mTasks.get(1)); invocation.callRealMethod(); return null; - }).when(mSupervisor).endActivityVisibilityUpdate(any(), anyInt(), anyBoolean(), - anyBoolean()); + }).when(mSupervisor).endActivityVisibilityUpdate(); mTaskContainer.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, false /* preserveWindows */, false /* notifyClients */); diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 13f04d23ccd3..2efd4b53efcc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -117,7 +117,6 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { assertEquals(win.mActivityRecord.getPrefixOrderIndex(), app.prefixOrderIndex); assertEquals(win.mActivityRecord.getTask().mTaskId, app.taskId); assertEquals(mMockLeash, app.leash); - assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect); assertEquals(false, app.isTranslucent); verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y); verify(mMockTransaction).setWindowCrop(mMockLeash, 100, 50); @@ -274,7 +273,6 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { assertEquals(new Rect(0, 0, 200, 200), app.startBounds); assertEquals(mMockLeash, app.leash); assertEquals(mMockThumbnailLeash, app.startLeash); - assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect); assertEquals(false, app.isTranslucent); verify(mMockTransaction).setPosition( mMockLeash, app.startBounds.left, app.startBounds.top); @@ -325,7 +323,6 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { assertEquals(new Rect(50, 100, 150, 150), app.startBounds); assertEquals(mMockLeash, app.leash); assertEquals(mMockThumbnailLeash, app.startLeash); - assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect); assertEquals(false, app.isTranslucent); verify(mMockTransaction).setPosition( mMockLeash, app.startBounds.left, app.startBounds.top); diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 3053fe6ec55f..cc8b2a1bb392 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -173,6 +173,35 @@ public class RootWindowContainerTests extends WindowTestsBase { } @Test + public void testTaskLayerRank() { + final Task rootTask = new TaskBuilder(mSupervisor).build(); + final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build(); + new ActivityBuilder(mAtm).setStack(task1).build().mVisibleRequested = true; + // RootWindowContainer#invalidateTaskLayers should post to update. + waitHandlerIdle(mWm.mH); + + assertEquals(1, task1.mLayerRank); + // Only tasks that directly contain activities have a ranking. + assertEquals(Task.LAYER_RANK_INVISIBLE, rootTask.mLayerRank); + + final Task task2 = new TaskBuilder(mSupervisor).build(); + new ActivityBuilder(mAtm).setStack(task2).build().mVisibleRequested = true; + waitHandlerIdle(mWm.mH); + + // Note that ensureActivitiesVisible is disabled in SystemServicesTestRule, so both the + // activities have the visible rank. + assertEquals(2, task1.mLayerRank); + // The task2 is the top task, so it has a lower rank as a higher priority oom score. + assertEquals(1, task2.mLayerRank); + + task2.moveToBack("test", null /* task */); + waitHandlerIdle(mWm.mH); + + assertEquals(1, task1.mLayerRank); + assertEquals(2, task2.mLayerRank); + } + + @Test public void testForceStopPackage() { final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); final ActivityRecord activity = task.getTopMostActivity(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java index d37f3f402c30..ea1223312cb2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java @@ -94,11 +94,6 @@ public class TestIWindow extends IWindow.Stub { } @Override - public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, int localValue, - int localChanges) throws RemoteException { - } - - @Override public void dispatchWindowShown() throws RemoteException { } diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java new file mode 100644 index 000000000000..ce22205c75f0 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -0,0 +1,178 @@ +/* + * 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.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_TASK_OPEN; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; + +import android.platform.test.annotations.Presubmit; +import android.util.ArrayMap; +import android.window.ITaskOrganizer; +import android.window.TransitionInfo; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Build/Install/Run: + * atest WmTests:TransitionRecordTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class TransitionTests extends WindowTestsBase { + + @Test + public void testCreateInfo_NewTask() { + final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + newTask.setHasBeenVisible(true); + oldTask.setHasBeenVisible(false); + final ActivityRecord closing = createActivityRecordInTask(oldTask); + final ActivityRecord opening = createActivityRecordInTask(newTask); + closing.setVisible(true); + closing.mVisibleRequested = false; + opening.setVisible(false); + opening.mVisibleRequested = true; + ArrayMap<WindowContainer, Transition.ChangeInfo> participants = new ArrayMap<>(); + + int transitType = TRANSIT_TASK_OPEN; + + // Check basic both tasks participating + participants.put(oldTask, new Transition.ChangeInfo()); + participants.put(newTask, new Transition.ChangeInfo()); + TransitionInfo info = + Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertEquals(transitType, info.getType()); + + // Check that children are pruned + participants.put(opening, new Transition.ChangeInfo()); + participants.put(closing, new Transition.ChangeInfo()); + info = Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); + assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); + + // Check combined prune and promote + participants.remove(newTask); + info = Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); + assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); + + // Check multi promote + participants.remove(oldTask); + info = Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); + assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); + } + + @Test + public void testCreateInfo_NestedTasks() { + final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task newNestedTask = createTaskInStack(newTask, 0); + final Task newNestedTask2 = createTaskInStack(newTask, 0); + final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + newTask.setHasBeenVisible(true); + oldTask.setHasBeenVisible(false); + final ActivityRecord closing = createActivityRecordInTask(oldTask); + final ActivityRecord opening = createActivityRecordInTask(newNestedTask); + final ActivityRecord opening2 = createActivityRecordInTask(newNestedTask2); + closing.setVisible(true); + closing.mVisibleRequested = false; + opening.setVisible(false); + opening.mVisibleRequested = true; + opening2.setVisible(false); + opening2.mVisibleRequested = true; + ArrayMap<WindowContainer, Transition.ChangeInfo> participants = new ArrayMap<>(); + + int transitType = TRANSIT_TASK_OPEN; + + // Check full promotion from leaf + participants.put(oldTask, new Transition.ChangeInfo()); + participants.put(opening, new Transition.ChangeInfo()); + participants.put(opening2, new Transition.ChangeInfo()); + TransitionInfo info = + Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertEquals(transitType, info.getType()); + assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); + assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); + + // Check that unchanging but visible descendant of sibling prevents promotion + participants.remove(opening2); + info = Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertNotNull(info.getChange(newNestedTask.mRemoteToken.toWindowContainerToken())); + assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); + } + + @Test + public void testCreateInfo_DisplayArea() { + final Task showTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task showNestedTask = createTaskInStack(showTask, 0); + final Task showTask2 = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final DisplayArea tda = showTask.getDisplayArea(); + showTask.setHasBeenVisible(true); + showTask2.setHasBeenVisible(true); + final ActivityRecord showing = createActivityRecordInTask(showNestedTask); + final ActivityRecord showing2 = createActivityRecordInTask(showTask2); + showing.setVisible(false); + showing.mVisibleRequested = true; + showing2.setVisible(false); + showing2.mVisibleRequested = true; + ArrayMap<WindowContainer, Transition.ChangeInfo> participants = new ArrayMap<>(); + + int transitType = TRANSIT_TASK_OPEN; + + // Check promotion to DisplayArea + participants.put(showing, new Transition.ChangeInfo()); + participants.put(showing2, new Transition.ChangeInfo()); + TransitionInfo info = + Transition.calculateTransitionInfo(transitType, participants); + assertEquals(1, info.getChanges().size()); + assertEquals(transitType, info.getType()); + assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken())); + + ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); + // Check that organized tasks get reported even if not top + showTask.mTaskOrganizer = mockOrg; + info = Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken())); + assertNotNull(info.getChange(showTask.mRemoteToken.toWindowContainerToken())); + // Even if DisplayArea explicitly participating + participants.put(tda, new Transition.ChangeInfo()); + info = Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java index ca3626d09062..0cf63f4ff21d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java @@ -115,12 +115,6 @@ public class WindowFrameTests extends WindowTestsBase { expectedRect.bottom); } - private void assertPolicyCrop(WindowState w, int left, int top, int right, int bottom) { - Rect policyCrop = new Rect(); - w.calculatePolicyCrop(policyCrop); - assertRect(policyCrop, left, top, right, bottom); - } - @Test public void testLayoutInFullscreenTaskInsets() { // fullscreen task doesn't use bounds for computeFrame @@ -335,12 +329,10 @@ public class WindowFrameTests extends WindowTestsBase { final WindowFrames windowFrames = w.getWindowFrames(); windowFrames.setFrames(pf, df, cf, vf, dcf, sf); w.computeFrame(); - assertPolicyCrop(w, 0, cf.top, logicalWidth, cf.bottom); windowFrames.mDecorFrame.setEmpty(); // Likewise with no decor frame we would get no crop w.computeFrame(); - assertPolicyCrop(w, 0, 0, logicalWidth, logicalHeight); // Now we set up a window which doesn't fill the entire decor frame. // Normally it would be cropped to it's frame but in the case of docked resizing @@ -355,16 +347,7 @@ public class WindowFrameTests extends WindowTestsBase { w.mRequestedHeight = logicalHeight / 2; w.computeFrame(); - // Normally the crop is shrunk from the decor frame - // to the computed window frame. - assertPolicyCrop(w, 0, 0, logicalWidth / 2, logicalHeight / 2); - doReturn(true).when(w).isDockedResizing(); - // But if we are docked resizing it won't be, however we will still be - // shrunk to the decor frame and the display. - assertPolicyCrop(w, 0, 0, - Math.min(pf.width(), displayInfo.logicalWidth), - Math.min(pf.height(), displayInfo.logicalHeight)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index 38c7531f5f5d..e50c00975a7f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -277,6 +277,69 @@ public class WindowProcessControllerTests extends WindowTestsBase { mWpc.getConfiguration().seq, globalSeq); } + @Test + public void testComputeOomAdjFromActivities() { + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setCreateTask(true) + .setUseProcess(mWpc) + .build(); + activity.mVisibleRequested = true; + final int[] callbackResult = { 0 }; + final int visible = 1; + final int paused = 2; + final int stopping = 4; + final int other = 8; + final WindowProcessController.ComputeOomAdjCallback callback = + new WindowProcessController.ComputeOomAdjCallback() { + @Override + public void onVisibleActivity() { + callbackResult[0] |= visible; + } + + @Override + public void onPausedActivity() { + callbackResult[0] |= paused; + } + + @Override + public void onStoppingActivity(boolean finishing) { + callbackResult[0] |= stopping; + } + + @Override + public void onOtherActivity() { + callbackResult[0] |= other; + } + }; + + // onStartActivity should refresh the state immediately. + mWpc.onStartActivity(0 /* topProcessState */, activity.info); + assertEquals(1 /* minTaskLayer */, mWpc.computeOomAdjFromActivities(callback)); + assertEquals(visible, callbackResult[0]); + + // The oom state will be updated in handler from activity state change. + callbackResult[0] = 0; + activity.mVisibleRequested = false; + activity.setState(Task.ActivityState.PAUSED, "test"); + waitHandlerIdle(mAtm.mH); + mWpc.computeOomAdjFromActivities(callback); + assertEquals(paused, callbackResult[0]); + + // updateProcessInfo with updateOomAdj=true should refresh the state immediately. + callbackResult[0] = 0; + activity.setState(Task.ActivityState.STOPPING, "test"); + mWpc.updateProcessInfo(false /* updateServiceConnectionActivities */, + true /* activityChange */, true /* updateOomAdj */, false /* addPendingTopUid */); + mWpc.computeOomAdjFromActivities(callback); + assertEquals(stopping, callbackResult[0]); + + callbackResult[0] = 0; + activity.setState(Task.ActivityState.STOPPED, "test"); + waitHandlerIdle(mAtm.mH); + mWpc.computeOomAdjFromActivities(callback); + assertEquals(other, callbackResult[0]); + } + private TestDisplayContent createTestDisplayContentInContainer() { return new TestDisplayContent.Builder(mAtm, 1000, 1500).build(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 7daddd8720ab..6237be0f4b26 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -265,6 +265,11 @@ class WindowTestsBase extends SystemServiceTestsBase { return activity; } + /** Creates an {@link ActivityRecord} and adds it to the specified {@link Task}. */ + static ActivityRecord createActivityRecordInTask(Task task) { + return createActivityRecordInTask(task.getDisplayContent(), task); + } + static ActivityRecord createTestActivityRecord(DisplayContent dc) { final ActivityRecord activity = new ActivityBuilder(dc.mWmService.mAtmService).build(); postCreateActivitySetup(activity, dc); @@ -360,7 +365,7 @@ class WindowTestsBase extends SystemServiceTestsBase { attrs.setTitle(name); final WindowState w = new WindowState(service, session, iWindow, token, parent, - OP_NONE, 0, attrs, VISIBLE, ownerId, userId, + OP_NONE, attrs, VISIBLE, ownerId, userId, ownerCanAddInternalSystemWindow, powerManagerWrapper); // TODO: Probably better to make this call in the WindowState ctor to avoid errors with @@ -1088,7 +1093,7 @@ class WindowTestsBase extends SystemServiceTestsBase { TestWindowState(WindowManagerService service, Session session, IWindow window, WindowManager.LayoutParams attrs, WindowToken token) { - super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, 0, + super(service, session, window, token, null, OP_NONE, attrs, 0, 0, 0, false /* ownerCanAddInternalSystemWindow */); } diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java index f7fe1ba1f998..1472a4ac27bc 100644 --- a/telecomm/java/android/telecom/DisconnectCause.java +++ b/telecomm/java/android/telecom/DisconnectCause.java @@ -16,7 +16,6 @@ package android.telecom; -import android.annotation.SystemApi; import android.media.ToneGenerator; import android.os.Parcel; import android.os.Parcelable; @@ -97,10 +96,7 @@ public final class DisconnectCause implements Parcelable { * * This reason code is only used for communication between a {@link ConnectionService} and * Telecom and should not be surfaced to the user. - * - * @hide */ - @SystemApi public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL"; /** diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index c20e5ad8ce7c..866e17148a1a 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -28,6 +28,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.TelephonyManager; import android.text.TextUtils; import java.util.ArrayList; @@ -48,12 +49,20 @@ import java.util.Objects; public final class PhoneAccount implements Parcelable { /** - * String extra which determines the order in which {@link PhoneAccount}s are sorted + * Integer extra which determines the order in which {@link PhoneAccount}s are sorted * * This is an extras key set via {@link Builder#setExtras} which determines the order in which * {@link PhoneAccount}s from the same {@link ConnectionService} are sorted. The accounts - * are sorted by this key via standard lexicographical order, and this ordering is used to + * are sorted in ascending order by this key, and this ordering is used to * determine priority when a call can be placed via multiple accounts. + * + * When multiple {@link PhoneAccount}s are supplied with the same sort order key, no ordering is + * guaranteed between those {@link PhoneAccount}s. Additionally, no ordering is guaranteed + * between {@link PhoneAccount}s that do not supply this extra, and all such accounts + * will be sorted after the accounts that do supply this extra. + * + * An example of a sort order key is slot index (see {@link TelephonyManager#getSlotIndex()}), + * which is the one used by the cell Telephony stack. * @hide */ @SystemApi diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt index 72a739c5c879..69caccb12d70 100644 --- a/telephony/api/system-current.txt +++ b/telephony/api/system-current.txt @@ -35,16 +35,12 @@ package android.telephony { method public int getTimeoutSeconds(); method public boolean isEnabled(); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR; - field public static final int ERROR_FDN_CHECK_FAILURE = 2; // 0x2 - field public static final int ERROR_NOT_SUPPORTED = 3; // 0x3 - field public static final int ERROR_UNKNOWN = 1; // 0x1 field public static final int REASON_ALL = 4; // 0x4 field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5 field public static final int REASON_BUSY = 1; // 0x1 field public static final int REASON_NOT_REACHABLE = 3; // 0x3 field public static final int REASON_NO_REPLY = 2; // 0x2 field public static final int REASON_UNCONDITIONAL = 0; // 0x0 - field public static final int SUCCESS = 0; // 0x0 } public final class CallQuality implements android.os.Parcelable { @@ -755,7 +751,7 @@ package android.telephony { method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingStatus(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int); @@ -854,6 +850,10 @@ package android.telephony { public static interface TelephonyManager.CallForwardingInfoCallback { method public void onCallForwardingInfoAvailable(@NonNull android.telephony.CallForwardingInfo); method public void onError(int); + field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 2; // 0x2 + field public static final int RESULT_ERROR_NOT_SUPPORTED = 3; // 0x3 + field public static final int RESULT_ERROR_UNKNOWN = 1; // 0x1 + field public static final int RESULT_SUCCESS = 0; // 0x0 } public final class UiccAccessRule implements android.os.Parcelable { diff --git a/telephony/java/android/telephony/CallForwardingInfo.java b/telephony/java/android/telephony/CallForwardingInfo.java index 2106f7fc4bb8..6ae6d002d990 100644 --- a/telephony/java/android/telephony/CallForwardingInfo.java +++ b/telephony/java/android/telephony/CallForwardingInfo.java @@ -39,27 +39,6 @@ public final class CallForwardingInfo implements Parcelable { private static final String TAG = "CallForwardingInfo"; /** - * Indicates that the operation was successful. - */ - public static final int SUCCESS = 0; - - /** - * Indicates that setting or retrieving the call forwarding info failed with an unknown error. - */ - public static final int ERROR_UNKNOWN = 1; - - /** - * Indicates that call forwarding is not enabled because the recipient is not on a - * Fixed Dialing Number (FDN) list. - */ - public static final int ERROR_FDN_CHECK_FAILURE = 2; - - /** - * Indicates that call forwarding is not supported on the network at this time. - */ - public static final int ERROR_NOT_SUPPORTED = 3; - - /** * Indicates that call forwarding reason is "unconditional". * Reference: 3GPP TS 27.007 version 10.3.0 Release 10 - 7.11 Call forwarding number * and conditions +CCFC @@ -104,19 +83,6 @@ public final class CallForwardingInfo implements Parcelable { public static final int REASON_ALL_CONDITIONAL = 5; /** - * Call forwarding errors - * @hide - */ - @IntDef(prefix = { "ERROR_" }, value = { - ERROR_UNKNOWN, - ERROR_NOT_SUPPORTED, - ERROR_FDN_CHECK_FAILURE - }) - @Retention(RetentionPolicy.SOURCE) - public @interface CallForwardingError{ - } - - /** * Call forwarding reason types * @hide */ diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 47bc566a331a..8261b53a2c9f 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -79,6 +79,30 @@ public class CarrierConfigManager { */ public static final int SERVICE_CLASS_VOICE = ImsSsData.SERVICE_CLASS_VOICE; + /** + * Only send USSD over IMS while CS is out of service, otherwise send USSD over CS. + * {@link #KEY_CARRIER_USSD_METHOD_INT} + */ + public static final int USSD_OVER_CS_PREFERRED = 0; + + /** + * Send USSD over IMS or CS while IMS is out of service or silent redial over CS if needed. + * {@link #KEY_CARRIER_USSD_METHOD_INT} + */ + public static final int USSD_OVER_IMS_PREFERRED = 1; + + /** + * Only send USSD over CS. + * {@link #KEY_CARRIER_USSD_METHOD_INT} + */ + public static final int USSD_OVER_CS_ONLY = 2; + + /** + * Only send USSD over IMS and disallow silent redial over CS. + * {@link #KEY_CARRIER_USSD_METHOD_INT} + */ + public static final int USSD_OVER_IMS_ONLY = 3; + private final Context mContext; /** @@ -584,6 +608,20 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool"; /** + * Specify the method of selection for UE sending USSD requests. The default value is + * {@link #USSD_OVER_CS_PREFERRED}. + * <p> Available options: + * <ul> + * <li>0: {@link #USSD_OVER_CS_PREFERRED} </li> + * <li>1: {@link #USSD_OVER_IMS_PREFERRED} </li> + * <li>2: {@link #USSD_OVER_CS_ONLY} </li> + * <li>3: {@link #USSD_OVER_IMS_ONLY} </li> + * </ul> + */ + public static final String KEY_CARRIER_USSD_METHOD_INT = + "carrier_ussd_method_int"; + + /** * Flag specifying whether to show an alert dialog for 5G disable when the user disables VoLTE. * By default this value is {@code false}. * @@ -3932,6 +3970,16 @@ public class CarrierConfigManager { public static final String KEY_DEFAULT_PREFERRED_APN_NAME_STRING = "default_preferred_apn_name_string"; + /** + * For Android 11, provide a temporary solution for OEMs to use the lower of the two MTU values + * for IPv4 and IPv6 if both are sent. + * TODO: remove in later release + * + * @hide + */ + public static final String KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED = + "use_lower_mtu_value_if_both_received"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -3953,6 +4001,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CARRIER_SETTINGS_ENABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VOLTE_AVAILABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VT_AVAILABLE_BOOL, false); + sDefaults.putInt(KEY_CARRIER_USSD_METHOD_INT, USSD_OVER_CS_PREFERRED); sDefaults.putBoolean(KEY_VOLTE_5G_LIMITED_ALERT_DIALOG_BOOL, false); sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false); sDefaults.putBoolean(KEY_ALLOW_MERGING_RTT_CALLS_BOOL, false); @@ -4473,6 +4522,7 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]); sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false); sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, ""); + sDefaults.putBoolean(KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED, false); } /** diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java index cdf735195d61..8f5ec365e65c 100644 --- a/telephony/java/android/telephony/CellLocation.java +++ b/telephony/java/android/telephony/CellLocation.java @@ -29,7 +29,10 @@ import com.android.internal.telephony.PhoneConstants; /** * Abstract class that represents the location of the device. {@more} + * + * @deprecated use {@link android.telephony.CellIdentity CellIdentity}. */ +@Deprecated public abstract class CellLocation { /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index a82d98807ad3..c60b514a7a9b 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -2807,7 +2807,11 @@ public class TelephonyManager { /** Current network is LTE_CA {@hide} */ @UnsupportedAppUsage public static final int NETWORK_TYPE_LTE_CA = TelephonyProtoEnums.NETWORK_TYPE_LTE_CA; // = 19. - /** Current network is NR(New Radio) 5G. */ + /** + * Current network is NR (New Radio) 5G. + * This will only be returned for 5G SA. + * For 5G NSA, the network type will be {@link #NETWORK_TYPE_LTE}. + */ public static final int NETWORK_TYPE_NR = TelephonyProtoEnums.NETWORK_TYPE_NR; // 20. private static final @NetworkType int[] NETWORK_TYPES = { @@ -13028,6 +13032,40 @@ public class TelephonyManager { @SystemApi public interface CallForwardingInfoCallback { /** + * Indicates that the operation was successful. + */ + int RESULT_SUCCESS = 0; + + /** + * Indicates that setting or retrieving the call forwarding info failed with an unknown + * error. + */ + int RESULT_ERROR_UNKNOWN = 1; + + /** + * Indicates that call forwarding is not enabled because the recipient is not on a + * Fixed Dialing Number (FDN) list. + */ + int RESULT_ERROR_FDN_CHECK_FAILURE = 2; + + /** + * Indicates that call forwarding is not supported on the network at this time. + */ + int RESULT_ERROR_NOT_SUPPORTED = 3; + + /** + * Call forwarding errors + * @hide + */ + @IntDef(prefix = { "RESULT_ERROR_" }, value = { + RESULT_ERROR_UNKNOWN, + RESULT_ERROR_NOT_SUPPORTED, + RESULT_ERROR_FDN_CHECK_FAILURE + }) + @Retention(RetentionPolicy.SOURCE) + @interface CallForwardingError{ + } + /** * Called when the call forwarding info is successfully retrieved from the network. * @param info information about how calls are forwarded */ @@ -13037,7 +13075,7 @@ public class TelephonyManager { * Called when there was an error retrieving the call forwarding information. * @param error */ - void onError(@CallForwardingInfo.CallForwardingError int error); + void onError(@CallForwardingError int error); } /** @@ -13110,9 +13148,9 @@ public class TelephonyManager { * @param executor The executor on which the listener will be called. Must be non-null if * {@code listener} is non-null. * @param resultListener Asynchronous listener that'll be called when the operation completes. - * Called with {@link CallForwardingInfo#SUCCESS} if the operation - * succeeded and an error code from {@link CallForwardingInfo} - * if it failed. + * Called with {@link CallForwardingInfoCallback#RESULT_SUCCESS} if the + * operation succeeded and an error code from + * {@link CallForwardingInfoCallback} it failed. * * @throws IllegalArgumentException if any of the following are true for the parameter * callForwardingInfo: @@ -13138,7 +13176,8 @@ public class TelephonyManager { @SystemApi public void setCallForwarding(@NonNull CallForwardingInfo callForwardingInfo, @Nullable @CallbackExecutor Executor executor, - @Nullable @CallForwardingInfo.CallForwardingError Consumer<Integer> resultListener) { + @Nullable @CallForwardingInfoCallback.CallForwardingError + Consumer<Integer> resultListener) { if (callForwardingInfo == null) { throw new IllegalArgumentException("callForwardingInfo is null"); } @@ -13294,7 +13333,7 @@ public class TelephonyManager { */ @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public void setCallWaitingStatus(boolean enabled, @Nullable Executor executor, + public void setCallWaitingEnabled(boolean enabled, @Nullable Executor executor, @Nullable Consumer<Integer> resultListener) { if (resultListener != null) { Objects.requireNonNull(executor); diff --git a/telephony/java/android/telephony/cdma/CdmaCellLocation.java b/telephony/java/android/telephony/cdma/CdmaCellLocation.java index 9bc39a0c6ced..d808cabaaa92 100644 --- a/telephony/java/android/telephony/cdma/CdmaCellLocation.java +++ b/telephony/java/android/telephony/cdma/CdmaCellLocation.java @@ -23,7 +23,10 @@ import android.telephony.CellLocation; /** * Represents the cell location on a CDMA phone. + * + * @deprecated use {@link android.telephony.CellIdentity CellIdentity}. */ +@Deprecated public class CdmaCellLocation extends CellLocation { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private int mBaseStationId = -1; diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index 39859b1e4fdb..579200e15cca 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -69,6 +69,7 @@ public final class DataCallResponse implements Parcelable { /** {@hide} */ @IntDef(prefix = "HANDOVER_FAILURE_MODE_", value = { + HANDOVER_FAILURE_MODE_UNKNOWN, HANDOVER_FAILURE_MODE_LEGACY, HANDOVER_FAILURE_MODE_DO_FALLBACK, HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER, diff --git a/telephony/java/android/telephony/gsm/GsmCellLocation.java b/telephony/java/android/telephony/gsm/GsmCellLocation.java index bc8ee1dd9359..2eee4ce371a0 100644 --- a/telephony/java/android/telephony/gsm/GsmCellLocation.java +++ b/telephony/java/android/telephony/gsm/GsmCellLocation.java @@ -23,7 +23,10 @@ import android.telephony.CellLocation; /** * Represents the cell location on a GSM phone. + * + * @deprecated use {@link android.telephony.CellIdentity CellIdentity}. */ +@Deprecated public class GsmCellLocation extends CellLocation { private int mLac; private int mCid; diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl index 7bbe30a444b9..52464703c608 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl @@ -27,8 +27,11 @@ import com.android.ims.internal.IImsCallSession; * See MmTelFeature#Listener for more information. * {@hide} */ -oneway interface IImsMmTelListener { + // This interface is not considered oneway because we need to ensure that these operations are + // processed by telephony before the control flow returns to the ImsService to perform + // operations on the IImsCallSession. +interface IImsMmTelListener { void onIncomingCall(IImsCallSession c, in Bundle extras); void onRejectedCall(in ImsCallProfile callProfile, in ImsReasonInfo reason); - void onVoiceMessageCountUpdate(int count); + oneway void onVoiceMessageCountUpdate(int count); } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 05a59ef7fc72..9a2def935f5d 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -745,7 +745,7 @@ </activity> <activity android:name="BlurActivity" - android:label="Shaders/Blur" + android:label="RenderEffect/Blur" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> diff --git a/tests/HwAccelerationTest/res/layout/image_filter_activity.xml b/tests/HwAccelerationTest/res/layout/image_filter_activity.xml new file mode 100644 index 000000000000..a0ee67ae0bef --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/image_filter_activity.xml @@ -0,0 +1,28 @@ +<?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. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center"> + + <ImageView + android:id="@+id/image_filter_test_view" + android:background="#FF0000" + android:layout_width="200dp" + android:layout_height="200dp" /> +</FrameLayout>
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java index 033fb0ec35d2..e4ca7881f796 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java @@ -18,11 +18,12 @@ package com.android.test.hwui; import android.app.Activity; import android.content.Context; -import android.graphics.BlurShader; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; +import android.graphics.RenderEffect; +import android.graphics.RenderNode; import android.graphics.Shader; import android.os.Bundle; import android.view.Gravity; @@ -51,16 +52,27 @@ public class BlurActivity extends Activity { } public static class BlurGradientView extends View { - private BlurShader mBlurShader = null; - private Paint mPaint; + private final float mBlurRadius = 25f; + private final Paint mPaint; + private final RenderNode mRenderNode; public BlurGradientView(Context c) { super(c); + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mRenderNode = new RenderNode("BlurGradientView"); + mRenderNode.setRenderEffect( + RenderEffect.createBlurEffect( + mBlurRadius, + mBlurRadius, + null, + Shader.TileMode.DECAL + ) + ); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (changed || mBlurShader == null) { + if (changed) { LinearGradient gradient = new LinearGradient( 0f, 0f, @@ -70,41 +82,81 @@ public class BlurActivity extends Activity { Color.YELLOW, Shader.TileMode.CLAMP ); - mBlurShader = new BlurShader(30f, 40f, gradient); - mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPaint.setShader(mBlurShader); + + mPaint.setShader(gradient); + + final int width = right - left; + final int height = bottom - top; + mRenderNode.setPosition(0, 0, width, height); + + Canvas canvas = mRenderNode.beginRecording(); + canvas.drawRect( + mBlurRadius * 2, + mBlurRadius * 2, + width - mBlurRadius * 2, + height - mBlurRadius * 2, + mPaint + ); + mRenderNode.endRecording(); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); + canvas.drawRenderNode(mRenderNode); } } public static class BlurView extends View { - private final BlurShader mBlurShader; private final Paint mPaint; + private final RenderNode mRenderNode; + private final float mBlurRadius = 20f; public BlurView(Context c) { super(c); - mBlurShader = new BlurShader(20f, 20f, null); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPaint.setShader(mBlurShader); + mRenderNode = new RenderNode("blurNode"); + mRenderNode.setRenderEffect( + RenderEffect.createBlurEffect( + mBlurRadius, + mBlurRadius, + null, + Shader.TileMode.DECAL + ) + ); } @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (changed) { + int width = right - left; + int height = bottom - top; + mRenderNode.setPosition(0, 0, width, height); + Canvas canvas = mRenderNode.beginRecording(width, height); + mPaint.setColor(Color.BLUE); + + canvas.drawRect( + mBlurRadius * 2, + mBlurRadius * 2, + width - mBlurRadius * 2, + height - mBlurRadius * 2, + mPaint + ); - mPaint.setColor(Color.BLUE); - canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); + mPaint.setColor(Color.RED); + canvas.drawCircle((right - left) / 2f, (bottom - top) / 2f, 50f, mPaint); + + mRenderNode.endRecording(); + } + } - mPaint.setColor(Color.RED); - canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, 50f, mPaint); + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawRenderNode(mRenderNode); } } } diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt index 0f62c4fa66a3..e9227e94da98 100644 --- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt +++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt @@ -107,7 +107,10 @@ class PlatformCompatCommandNotInstalledTest { fun ParcelFileDescriptor.text() = FileReader(fileDescriptor).readText() @After - fun resetIdentity() = uiAutomation.dropShellPermissionIdentity() + fun resetChangeIdAndIdentity() { + command("am compat reset $TEST_CHANGE_ID $TEST_PKG") + uiAutomation.dropShellPermissionIdentity() + } @Test fun execute() { diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt index 711758476a62..2f2578b87f35 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt @@ -20,13 +20,13 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.BitmapShader import android.graphics.BlendMode -import android.graphics.BlurShader import android.graphics.Canvas import android.graphics.Color import android.graphics.Outline import android.graphics.Paint -import android.graphics.RadialGradient import android.graphics.Rect +import android.graphics.RenderEffect +import android.graphics.RenderNode import android.graphics.Shader import android.hardware.Sensor import android.hardware.SensorEvent @@ -36,7 +36,6 @@ import android.util.AttributeSet import android.view.View import android.view.ViewOutlineProvider import android.widget.FrameLayout -import com.android.internal.graphics.ColorUtils import com.android.test.silkfx.R import kotlin.math.sin import kotlin.math.sqrt @@ -152,10 +151,19 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont var blurRadius = 150f set(value) { field = value - blurPaint.shader = BlurShader(value, value, null) + renderNode.setRenderEffect( + RenderEffect.createBlurEffect(value, value, Shader.TileMode.CLAMP)) invalidate() } + private var renderNodeIsDirty = true + private val renderNode = RenderNode("GlassRenderNode") + + override fun invalidate() { + renderNodeIsDirty = true + super.invalidate() + } + init { setWillNotDraw(false) materialPaint.blendMode = BlendMode.SOFT_LIGHT @@ -164,7 +172,6 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont scrimPaint.alpha = (scrimOpacity * 255).toInt() noisePaint.alpha = (noiseOpacity * 255).toInt() materialPaint.alpha = (materialOpacity * 255).toInt() - blurPaint.shader = BlurShader(blurRadius, blurRadius, null) outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View?, outline: Outline?) { outline?.setRoundRect(Rect(0, 0, width, height), 100f) @@ -184,20 +191,8 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont } override fun onDraw(canvas: Canvas?) { - src.set(-width / 2, -height / 2, width / 2, height / 2) - src.scale(1.0f + zoom) - val centerX = left + width / 2 - val centerY = top + height / 2 - val textureXOffset = (textureTranslationMultiplier * gyroYRotation).toInt() - val textureYOffset = (textureTranslationMultiplier * gyroXRotation).toInt() - src.set(src.left + centerX + textureXOffset, src.top + centerY + textureYOffset, - src.right + centerX + textureXOffset, src.bottom + centerY + textureYOffset) - - dst.set(0, 0, width, height) - canvas?.drawBitmap(backgroundBitmap, src, dst, blurPaint) - canvas?.drawRect(dst, materialPaint) - canvas?.drawRect(dst, noisePaint) - canvas?.drawRect(dst, scrimPaint) + updateGlassRenderNode() + canvas?.drawRenderNode(renderNode) } fun resetGyroOffsets() { @@ -205,4 +200,31 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont gyroYRotation = 0f invalidate() } + + private fun updateGlassRenderNode() { + if (renderNodeIsDirty) { + renderNode.setPosition(0, 0, getWidth(), getHeight()) + + val canvas = renderNode.beginRecording() + + src.set(-width / 2, -height / 2, width / 2, height / 2) + src.scale(1.0f + zoom) + val centerX = left + width / 2 + val centerY = top + height / 2 + val textureXOffset = (textureTranslationMultiplier * gyroYRotation).toInt() + val textureYOffset = (textureTranslationMultiplier * gyroXRotation).toInt() + src.set(src.left + centerX + textureXOffset, src.top + centerY + textureYOffset, + src.right + centerX + textureXOffset, src.bottom + centerY + textureYOffset) + + dst.set(0, 0, width, height) + canvas.drawBitmap(backgroundBitmap, src, dst, blurPaint) + canvas.drawRect(dst, materialPaint) + canvas.drawRect(dst, noisePaint) + canvas.drawRect(dst, scrimPaint) + + renderNode.endRecording() + + renderNodeIsDirty = false + } + } }
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/Android.bp b/tests/SurfaceViewBufferTests/Android.bp new file mode 100644 index 000000000000..647da2abd213 --- /dev/null +++ b/tests/SurfaceViewBufferTests/Android.bp @@ -0,0 +1,58 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test { + name: "SurfaceViewBufferTests", + srcs: ["**/*.java","**/*.kt"], + manifest: "AndroidManifest.xml", + test_config: "AndroidTest.xml", + platform_apis: true, + certificate: "platform", + use_embedded_native_libs: true, + jni_libs: [ + "libsurface_jni", + ], + + static_libs: [ + "androidx.appcompat_appcompat", + "androidx.test.rules", + "androidx.test.runner", + "androidx.test.ext.junit", + "kotlin-stdlib", + "kotlinx-coroutines-android", + "flickerlib", + "truth-prebuilt", + ], +} + +cc_library_shared { + name: "libsurface_jni", + srcs: [ + "cpp/SurfaceProxy.cpp", + ], + shared_libs: [ + "libutils", + "libgui", + "liblog", + "libandroid", + ], + include_dirs: [ + "system/core/include" + ], + stl: "libc++_static", + cflags: [ + "-Werror", + "-Wall", + ], +} diff --git a/tests/SurfaceViewBufferTests/AndroidManifest.xml b/tests/SurfaceViewBufferTests/AndroidManifest.xml new file mode 100644 index 000000000000..95885c1ca635 --- /dev/null +++ b/tests/SurfaceViewBufferTests/AndroidManifest.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test"> + + <uses-sdk android:minSdkVersion="29" + android:targetSdkVersion="29"/> + <!-- Enable / Disable tracing !--> + <uses-permission android:name="android.permission.DUMP" /> + <!-- Enable / Disable sv blast adapter !--> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + + <application android:allowBackup="false" + android:supportsRtl="true"> + <activity android:name=".MainActivity" + android:taskAffinity="com.android.test.MainActivity" + android:theme="@style/AppTheme" + android:label="SurfaceViewBufferTestApp" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <uses-library android:name="android.test.runner"/> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.test" + android:label="SurfaceViewBufferTests"> + </instrumentation> +</manifest> diff --git a/tests/SurfaceViewBufferTests/AndroidTest.xml b/tests/SurfaceViewBufferTests/AndroidTest.xml new file mode 100644 index 000000000000..b73fe4853ecf --- /dev/null +++ b/tests/SurfaceViewBufferTests/AndroidTest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs SurfaceView Buffer Tests"> + <option name="test-tag" value="SurfaceViewBufferTests" /> + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <!-- keeps the screen on during tests --> + <option name="screen-always-on" value="on" /> + <!-- prevents the phone from restarting --> + <option name="force-skip-system-props" value="true" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="false"/> + <option name="test-file-name" value="SurfaceViewBufferTests.apk"/> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.test"/> + <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" /> + <option name="shell-timeout" value="6600s" /> + <option name="test-timeout" value="6000s" /> + <option name="hidden-api-checks" value="false" /> + </test> +</configuration> diff --git a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp new file mode 100644 index 000000000000..0c86524293e7 --- /dev/null +++ b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp @@ -0,0 +1,102 @@ +/* + * 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. + */ + +#include <android/log.h> +#include <android/native_window.h> +#include <android/native_window_jni.h> +#include <android/window.h> +#include <gui/Surface.h> +#include <jni.h> +#include <system/window.h> +#include <utils/RefBase.h> +#include <cassert> +#include <chrono> +#include <thread> + +#define TAG "SurfaceViewBufferTests" +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) + +extern "C" { +int i = 0; +static ANativeWindow* sAnw; + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_setSurface(JNIEnv* env, jclass, + jobject surfaceObject) { + sAnw = ANativeWindow_fromSurface(env, surfaceObject); + assert(sAnw); + android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw); + surface->enableFrameTimestamps(true); + return 0; +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_waitUntilBufferDisplayed( + JNIEnv*, jclass, jint jFrameNumber, jint timeoutSec) { + using namespace std::chrono_literals; + assert(sAnw); + android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw); + + uint64_t frameNumber = static_cast<uint64_t>(jFrameNumber); + nsecs_t outRequestedPresentTime, outAcquireTime, outLatchTime, outFirstRefreshStartTime; + nsecs_t outLastRefreshStartTime, outGlCompositionDoneTime, outDequeueReadyTime; + nsecs_t outDisplayPresentTime = -1; + nsecs_t outReleaseTime; + + auto start = std::chrono::steady_clock::now(); + while (outDisplayPresentTime < 0) { + std::this_thread::sleep_for(8ms); + surface->getFrameTimestamps(frameNumber, &outRequestedPresentTime, &outAcquireTime, + &outLatchTime, &outFirstRefreshStartTime, + &outLastRefreshStartTime, &outGlCompositionDoneTime, + &outDisplayPresentTime, &outDequeueReadyTime, &outReleaseTime); + if (outDisplayPresentTime < 0) { + auto end = std::chrono::steady_clock::now(); + if (std::chrono::duration_cast<std::chrono::seconds>(end - start).count() > + timeoutSec) { + return -1; + } + } + } + return 0; +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_draw(JNIEnv*, jclass) { + assert(sAnw); + ANativeWindow_Buffer outBuffer; + ANativeWindow_lock(sAnw, &outBuffer, nullptr); + return 0; +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowLock(JNIEnv*, jclass) { + assert(sAnw); + ANativeWindow_Buffer outBuffer; + ANativeWindow_lock(sAnw, &outBuffer, nullptr); + return 0; +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowUnlockAndPost(JNIEnv*, + jclass) { + assert(sAnw); + ANativeWindow_unlockAndPost(sAnw); + return 0; +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowSetBuffersGeometry( + JNIEnv* /* env */, jclass /* clazz */, jobject /* surfaceObject */, jint w, jint h, + jint format) { + assert(sAnw); + return ANativeWindow_setBuffersGeometry(sAnw, w, h, format); +} +}
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/res/values/styles.xml b/tests/SurfaceViewBufferTests/res/values/styles.xml new file mode 100644 index 000000000000..8b50738a06de --- /dev/null +++ b/tests/SurfaceViewBufferTests/res/values/styles.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> +<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> + <item name="windowNoTitle">true</item> + <item name="windowActionBar">false</item> + <item name="android:windowFullscreen">true</item> + <item name="android:windowContentOverlay">@null</item> + <item name="android:windowDisablePreview">true</item> +</style> +</resources> diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt b/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt new file mode 100644 index 000000000000..b1e1336c4f6d --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt @@ -0,0 +1,102 @@ +/* + * 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.test + +import android.app.Activity +import android.content.Context +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import android.os.Bundle +import android.view.Gravity +import android.view.Surface +import android.view.SurfaceHolder +import android.view.SurfaceView +import android.widget.FrameLayout +import java.util.concurrent.CountDownLatch +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +class MainActivity : Activity() { + val mSurfaceProxy = SurfaceProxy() + private var mSurfaceHolder: SurfaceHolder? = null + private val mDrawLock = ReentrantLock() + + val surface: Surface? get() = mSurfaceHolder?.surface + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + addSurfaceView(Rect(0, 0, 500, 200)) + } + + fun addSurfaceView(size: Rect): CountDownLatch { + val layout = findViewById<FrameLayout>(android.R.id.content) + val surfaceReadyLatch = CountDownLatch(1) + val surfaceView = createSurfaceView(applicationContext, size, surfaceReadyLatch) + layout.addView(surfaceView, + FrameLayout.LayoutParams(size.width(), size.height(), Gravity.TOP or Gravity.LEFT) + .also { it.setMargins(100, 100, 0, 0) }) + return surfaceReadyLatch + } + + private fun createSurfaceView( + context: Context, + size: Rect, + surfaceReadyLatch: CountDownLatch + ): SurfaceView { + val surfaceView = SurfaceView(context) + surfaceView.setWillNotDraw(false) + surfaceView.holder.setFixedSize(size.width(), size.height()) + surfaceView.holder.addCallback(object : SurfaceHolder.Callback { + override fun surfaceCreated(holder: SurfaceHolder) { + mDrawLock.withLock { + mSurfaceHolder = holder + mSurfaceProxy.setSurface(holder.surface) + } + surfaceReadyLatch.countDown() + } + + override fun surfaceChanged( + holder: SurfaceHolder, + format: Int, + width: Int, + height: Int + ) { + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + mDrawLock.withLock { + mSurfaceHolder = null + } + } + }) + return surfaceView + } + + fun drawFrame(): Rect { + mDrawLock.withLock { + val holder = mSurfaceHolder ?: return Rect() + val canvas = holder.lockCanvas() + val canvasSize = Rect(0, 0, canvas.width, canvas.height) + canvas.drawColor(Color.GREEN) + val p = Paint() + p.color = Color.RED + canvas.drawRect(canvasSize, p) + holder.unlockCanvasAndPost(canvas) + return canvasSize + } + } +}
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt new file mode 100644 index 000000000000..884aae41446c --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt @@ -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.test + +class SurfaceProxy { + init { + System.loadLibrary("surface_jni") + } + + external fun setSurface(surface: Any) + external fun waitUntilBufferDisplayed(frameNumber: Int, timeoutSec: Int) + external fun draw() + + // android/native_window.h functions + external fun ANativeWindowLock() + external fun ANativeWindowUnlockAndPost() + external fun ANativeWindowSetBuffersGeometry(surface: Any, width: Int, height: Int, format: Int) +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt new file mode 100644 index 000000000000..b48a91d49b91 --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt @@ -0,0 +1,185 @@ +/* + * 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.test + +import android.app.Instrumentation +import android.graphics.Rect +import android.provider.Settings +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.monitor.LayersTraceMonitor +import com.android.server.wm.flicker.monitor.withSFTracing +import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import java.util.concurrent.CountDownLatch +import kotlin.properties.Delegates + +@RunWith(Parameterized::class) +class SurfaceViewBufferTest(val useBlastAdapter: Boolean) { + private var mInitialUseBlastConfig by Delegates.notNull<Int>() + + @get:Rule + var scenarioRule: ActivityScenarioRule<MainActivity> = + ActivityScenarioRule<MainActivity>(MainActivity::class.java) + + protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + val defaultBufferSize = Rect(0, 0, 640, 480) + + @Before + fun setup() { + mInitialUseBlastConfig = Settings.Global.getInt(instrumentation.context.contentResolver, + "use_blast_adapter_sv", 0) + val enable = if (useBlastAdapter) 1 else 0 + Settings.Global.putInt(instrumentation.context.contentResolver, "use_blast_adapter_sv", + enable) + val tmpDir = instrumentation.targetContext.dataDir.toPath() + LayersTraceMonitor(tmpDir).stop() + + lateinit var surfaceReadyLatch: CountDownLatch + scenarioRule.getScenario().onActivity { + surfaceReadyLatch = it.addSurfaceView(defaultBufferSize) + } + surfaceReadyLatch.await() + } + + @After + fun teardown() { + scenarioRule.getScenario().close() + Settings.Global.putInt(instrumentation.context.contentResolver, + "use_blast_adapter_sv", mInitialUseBlastConfig) + } + + @Test + fun testSetBuffersGeometry_0x0_resetsBufferSize() { + val trace = withSFTracing(instrumentation, TRACE_FLAGS) { + scenarioRule.getScenario().onActivity { + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, + R8G8B8A8_UNORM) + it.mSurfaceProxy.ANativeWindowLock() + it.mSurfaceProxy.ANativeWindowUnlockAndPost() + it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */) + } + } + + // verify buffer size is reset to default buffer size + assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) + } + + @Test + fun testSetBuffersGeometry_0x0_rejectsBuffer() { + val trace = withSFTracing(instrumentation, TRACE_FLAGS) { + scenarioRule.getScenario().onActivity { + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100, + R8G8B8A8_UNORM) + it.mSurfaceProxy.ANativeWindowLock() + it.mSurfaceProxy.ANativeWindowUnlockAndPost() + it.mSurfaceProxy.ANativeWindowLock() + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, R8G8B8A8_UNORM) + // Submit buffer one with a different size which should be rejected + it.mSurfaceProxy.ANativeWindowUnlockAndPost() + + // submit a buffer with the default buffer size + it.mSurfaceProxy.ANativeWindowLock() + it.mSurfaceProxy.ANativeWindowUnlockAndPost() + it.mSurfaceProxy.waitUntilBufferDisplayed(3, 1 /* sec */) + } + } + // Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE + assertThat(trace).layer("SurfaceView", 2).doesNotExist() + + // Verify the next buffer is submitted with the correct size + assertThat(trace).layer("SurfaceView", 3).also { + it.hasBufferSize(defaultBufferSize) + it.hasScalingMode(0 /* NATIVE_WINDOW_SCALING_MODE_FREEZE */) + } + } + + @Test + fun testSetBuffersGeometry_smallerThanBuffer() { + val bufferSize = Rect(0, 0, 300, 200) + val trace = withSFTracing(instrumentation, TRACE_FLAGS) { + scenarioRule.getScenario().onActivity { + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(), + bufferSize.height(), R8G8B8A8_UNORM) + it.drawFrame() + it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */) + } + } + + assertThat(trace).layer("SurfaceView", 1).also { + it.hasBufferSize(bufferSize) + it.hasLayerSize(defaultBufferSize) + it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */) + } + } + + @Test + fun testSetBuffersGeometry_largerThanBuffer() { + val bufferSize = Rect(0, 0, 3000, 2000) + val trace = withSFTracing(instrumentation, TRACE_FLAGS) { + scenarioRule.getScenario().onActivity { + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(), + bufferSize.height(), R8G8B8A8_UNORM) + it.drawFrame() + it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */) + } + } + + assertThat(trace).layer("SurfaceView", 1).also { + it.hasBufferSize(bufferSize) + it.hasLayerSize(defaultBufferSize) + it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */) + } + } + + /** Submit buffers as fast as possible and make sure they are queued */ + @Test + fun testQueueBuffers() { + val trace = withSFTracing(instrumentation, TRACE_FLAGS) { + scenarioRule.getScenario().onActivity { + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100, + R8G8B8A8_UNORM) + for (i in 0..100) { + it.mSurfaceProxy.ANativeWindowLock() + it.mSurfaceProxy.ANativeWindowUnlockAndPost() + } + it.mSurfaceProxy.waitUntilBufferDisplayed(100, 1 /* sec */) + } + } + for (frameNumber in 1..100) { + assertThat(trace).layer("SurfaceView", frameNumber.toLong()) + } + } + + companion object { + private const val TRACE_FLAGS = 0x1 // TRACE_CRITICAL + private const val R8G8B8A8_UNORM = 1 + + @JvmStatic + @Parameterized.Parameters(name = "blast={0}") + fun data(): Collection<Array<Any>> { + return listOf( + arrayOf(false), // First test: submit buffers via bufferqueue + arrayOf(true) // Second test: submit buffers via blast adapter + ) + } + } +}
\ No newline at end of file diff --git a/tests/net/Android.bp b/tests/net/Android.bp index 124b6609f687..0fe84abcbc7b 100644 --- a/tests/net/Android.bp +++ b/tests/net/Android.bp @@ -63,6 +63,7 @@ android_test { "services.net", ], libs: [ + "android.net.ipsec.ike.stubs.module_lib", "android.test.runner", "android.test.base", "android.test.mock", diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java index 3c3076f11727..030ddd2792bb 100644 --- a/tests/net/common/java/android/net/LinkPropertiesTest.java +++ b/tests/net/common/java/android/net/LinkPropertiesTest.java @@ -32,7 +32,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.net.LinkProperties.ProvisioningChange; -import android.net.util.LinkPropertiesUtils.CompareResult; import android.os.Build; import android.system.OsConstants; import android.util.ArraySet; @@ -41,6 +40,7 @@ import androidx.core.os.BuildCompat; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.net.module.util.LinkPropertiesUtils.CompareResult; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java index 91c9a2a38036..6de31f6b4be1 100644 --- a/tests/net/java/android/net/MacAddressTest.java +++ b/tests/net/java/android/net/MacAddressTest.java @@ -22,11 +22,11 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.net.util.MacAddressUtils; - import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.net.module.util.MacAddressUtils; + import org.junit.Test; import org.junit.runner.RunWith; diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index c76b4cd501e7..c3f1549409fc 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -20,6 +20,7 @@ import static android.content.pm.UserInfo.FLAG_ADMIN; import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; import static android.content.pm.UserInfo.FLAG_PRIMARY; import static android.content.pm.UserInfo.FLAG_RESTRICTED; +import static android.net.ConnectivityManager.NetworkCallback; import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; @@ -45,7 +46,9 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -66,6 +69,7 @@ import android.net.Ikev2VpnProfile; import android.net.InetAddresses; import android.net.IpPrefix; import android.net.IpSecManager; +import android.net.IpSecTunnelInterfaceResponse; import android.net.LinkProperties; import android.net.LocalSocket; import android.net.Network; @@ -75,6 +79,8 @@ import android.net.RouteInfo; import android.net.UidRange; import android.net.VpnManager; import android.net.VpnService; +import android.net.ipsec.ike.IkeSessionCallback; +import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.ConditionVariable; @@ -101,6 +107,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; +import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -150,6 +157,11 @@ public class VpnTest { private static final String TEST_VPN_IDENTITY = "identity"; private static final byte[] TEST_VPN_PSK = "psk".getBytes(); + private static final Network TEST_NETWORK = new Network(Integer.MAX_VALUE); + private static final String TEST_IFACE_NAME = "TEST_IFACE"; + private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345; + private static final long TEST_TIMEOUT_MS = 500L; + /** * Names and UIDs for some fake packages. Important points: * - UID is ordered increasing. @@ -227,6 +239,13 @@ public class VpnTest { // Deny all appops by default. when(mAppOps.noteOpNoThrow(anyInt(), anyInt(), anyString())) .thenReturn(AppOpsManager.MODE_IGNORED); + + // Setup IpSecService + final IpSecTunnelInterfaceResponse tunnelResp = + new IpSecTunnelInterfaceResponse( + IpSecManager.Status.OK, TEST_TUNNEL_RESOURCE_ID, TEST_IFACE_NAME); + when(mIpSecService.createTunnelInterface(any(), any(), any(), any(), any())) + .thenReturn(tunnelResp); } @Test @@ -988,6 +1007,52 @@ public class VpnTest { eq(AppOpsManager.MODE_IGNORED)); } + private NetworkCallback triggerOnAvailableAndGetCallback() { + final ArgumentCaptor<NetworkCallback> networkCallbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); + verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)) + .requestNetwork(any(), networkCallbackCaptor.capture()); + + final NetworkCallback cb = networkCallbackCaptor.getValue(); + cb.onAvailable(TEST_NETWORK); + return cb; + } + + @Test + public void testStartPlatformVpnAuthenticationFailed() throws Exception { + final ArgumentCaptor<IkeSessionCallback> captor = + ArgumentCaptor.forClass(IkeSessionCallback.class); + final IkeProtocolException exception = mock(IkeProtocolException.class); + when(exception.getErrorType()) + .thenReturn(IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED); + + final Vpn vpn = startLegacyVpn(mVpnProfile); + final NetworkCallback cb = triggerOnAvailableAndGetCallback(); + + // Wait for createIkeSession() to be called before proceeding in order to ensure consistent + // state + verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS)) + .createIkeSession(any(), any(), any(), any(), captor.capture(), any()); + final IkeSessionCallback ikeCb = captor.getValue(); + ikeCb.onClosedExceptionally(exception); + + verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb)); + assertEquals(DetailedState.FAILED, vpn.getNetworkInfo().getDetailedState()); + } + + @Test + public void testStartPlatformVpnIllegalArgumentExceptionInSetup() throws Exception { + when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any())) + .thenThrow(new IllegalArgumentException()); + final Vpn vpn = startLegacyVpn(mVpnProfile); + final NetworkCallback cb = triggerOnAvailableAndGetCallback(); + + // Wait for createIkeSession() to be called before proceeding in order to ensure consistent + // state + verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb)); + assertEquals(DetailedState.FAILED, vpn.getNetworkInfo().getDetailedState()); + } + private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) { assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore)); diff --git a/tools/xmlpersistence/Android.bp b/tools/xmlpersistence/Android.bp new file mode 100644 index 000000000000..d58d0dcdc45a --- /dev/null +++ b/tools/xmlpersistence/Android.bp @@ -0,0 +1,11 @@ +java_binary_host { + name: "xmlpersistence_cli", + manifest: "manifest.txt", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "javaparser-symbol-solver", + "javapoet", + ], +} diff --git a/tools/xmlpersistence/OWNERS b/tools/xmlpersistence/OWNERS new file mode 100644 index 000000000000..4f4d06a32676 --- /dev/null +++ b/tools/xmlpersistence/OWNERS @@ -0,0 +1 @@ +zhanghai@google.com diff --git a/tools/xmlpersistence/manifest.txt b/tools/xmlpersistence/manifest.txt new file mode 100644 index 000000000000..6d9771998efc --- /dev/null +++ b/tools/xmlpersistence/manifest.txt @@ -0,0 +1 @@ +Main-class: MainKt diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt new file mode 100644 index 000000000000..28467b7fc0b0 --- /dev/null +++ b/tools/xmlpersistence/src/main/kotlin/Generator.kt @@ -0,0 +1,578 @@ +/* + * 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. + */ + +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.FieldSpec +import com.squareup.javapoet.JavaFile +import com.squareup.javapoet.MethodSpec +import com.squareup.javapoet.NameAllocator +import com.squareup.javapoet.ParameterSpec +import com.squareup.javapoet.TypeSpec +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException +import java.nio.charset.StandardCharsets +import java.time.Year +import java.util.Objects +import javax.lang.model.element.Modifier + +// JavaPoet only supports line comments, and can't add a newline after file level comments. +val FILE_HEADER = """ + /* + * Copyright (C) ${Year.now().value} The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + // Generated by xmlpersistence. DO NOT MODIFY! + // CHECKSTYLE:OFF + // @formatter:off +""".trimIndent() + "\n\n" + +private val atomicFileType = ClassName.get("android.util", "AtomicFile") + +fun generate(persistence: PersistenceInfo): JavaFile { + val distinctClassFields = persistence.root.allClassFields.distinctBy { it.type } + val type = TypeSpec.classBuilder(persistence.name) + .addJavadoc( + """ + Generated class implementing XML persistence for${'$'}W{@link $1T}. + <p> + This class provides atomicity for persistence via {@link $2T}, however it does not provide + thread safety, so please bring your own synchronization mechanism. + """.trimIndent(), persistence.root.type, atomicFileType + ) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addField(generateFileField()) + .addMethod(generateConstructor()) + .addMethod(generateReadMethod(persistence.root)) + .addMethod(generateParseMethod(persistence.root)) + .addMethods(distinctClassFields.map { generateParseClassMethod(it) }) + .addMethod(generateWriteMethod(persistence.root)) + .addMethod(generateSerializeMethod(persistence.root)) + .addMethods(distinctClassFields.map { generateSerializeClassMethod(it) }) + .addMethod(generateDeleteMethod()) + .build() + return JavaFile.builder(persistence.root.type.packageName(), type) + .skipJavaLangImports(true) + .indent(" ") + .build() +} + +private val nonNullType = ClassName.get("android.annotation", "NonNull") + +private fun generateFileField(): FieldSpec = + FieldSpec.builder(atomicFileType, "mFile", Modifier.PRIVATE, Modifier.FINAL) + .addAnnotation(nonNullType) + .build() + +private fun generateConstructor(): MethodSpec = + MethodSpec.constructorBuilder() + .addJavadoc( + """ + Create an instance of this class. + + @param file the XML file for persistence + """.trimIndent() + ) + .addModifiers(Modifier.PUBLIC) + .addParameter( + ParameterSpec.builder(File::class.java, "file").addAnnotation(nonNullType).build() + ) + .addStatement("mFile = new \$1T(file)", atomicFileType) + .build() + +private val nullableType = ClassName.get("android.annotation", "Nullable") + +private val xmlPullParserType = ClassName.get("org.xmlpull.v1", "XmlPullParser") + +private val xmlType = ClassName.get("android.util", "Xml") + +private val xmlPullParserExceptionType = ClassName.get("org.xmlpull.v1", "XmlPullParserException") + +private fun generateReadMethod(rootField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder("read") + .addJavadoc( + """ + Read${'$'}W{@link $1T}${'$'}Wfrom${'$'}Wthe${'$'}WXML${'$'}Wfile. + + @return the persisted${'$'}W{@link $1T},${'$'}Wor${'$'}W{@code null}${'$'}Wif${'$'}Wthe${'$'}WXML${'$'}Wfile${'$'}Wdoesn't${'$'}Wexist + @throws IllegalArgumentException if an error occurred while reading + """.trimIndent(), rootField.type + ) + .addAnnotation(nullableType) + .addModifiers(Modifier.PUBLIC) + .returns(rootField.type) + .addControlFlow( + "try (final \$1T inputStream = mFile.openRead())", FileInputStream::class.java + ) { + addStatement("final \$1T parser = \$2T.newPullParser()", xmlPullParserType, xmlType) + addStatement("parser.setInput(inputStream, null)") + addStatement("return parse(parser)") + nextControlFlow("catch (\$1T e)", FileNotFoundException::class.java) + addStatement("return null") + nextControlFlow( + "catch (\$1T | \$2T e)", IOException::class.java, xmlPullParserExceptionType + ) + addStatement("throw new IllegalArgumentException(e)") + } + .build() + +private val ClassFieldInfo.allClassFields: List<ClassFieldInfo> + get() = + mutableListOf<ClassFieldInfo>().apply { + this += this@allClassFields + for (field in fields) { + when (field) { + is ClassFieldInfo -> this += field.allClassFields + is ListFieldInfo -> this += field.element.allClassFields + } + } + } + +private fun generateParseMethod(rootField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder("parse") + .addAnnotation(nonNullType) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(rootField.type) + .addParameter( + ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build() + ) + .addExceptions(listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType)) + .apply { + addStatement("int type") + addStatement("int depth") + addStatement("int innerDepth = parser.getDepth() + 1") + addControlFlow( + "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W" + + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))", + xmlPullParserType + ) { + addControlFlow( + "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType + ) { + addStatement("continue") + } + addControlFlow( + "if (\$1T.equals(parser.getName(),\$W\$2S))", Objects::class.java, + rootField.tagName + ) { + addStatement("return \$1L(parser)", rootField.parseMethodName) + } + } + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Missing root tag <${rootField.tagName}>" + ) + } + .build() + +private fun generateParseClassMethod(classField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder(classField.parseMethodName) + .addAnnotation(nonNullType) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(classField.type) + .addParameter( + ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build() + ) + .apply { + val (attributeFields, tagFields) = classField.fields + .partition { it is PrimitiveFieldInfo || it is StringFieldInfo } + if (tagFields.isNotEmpty()) { + addExceptions( + listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType) + ) + } + val nameAllocator = NameAllocator().apply { + newName("parser") + newName("type") + newName("depth") + newName("innerDepth") + } + for (field in attributeFields) { + val variableName = nameAllocator.newName(field.variableName, field) + when (field) { + is PrimitiveFieldInfo -> { + val stringVariableName = + nameAllocator.newName("${field.variableName}String") + addStatement( + "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)", + stringVariableName, field.attributeName + ) + if (field.isRequired) { + addControlFlow("if (\$1L == null)", stringVariableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Missing attribute \"${field.attributeName}\"" + ) + } + } + val boxedType = field.type.box() + val parseTypeMethodName = if (field.type.isPrimitive) { + "parse${field.type.toString().capitalize()}" + } else { + "valueOf" + } + if (field.isRequired) { + addStatement( + "final \$1T \$2L =\$W\$3T.\$4L($5L)", field.type, variableName, + boxedType, parseTypeMethodName, stringVariableName + ) + } else { + addStatement( + "final \$1T \$2L =\$W$3L != null ?\$W\$4T.\$5L($3L)\$W: null", + field.type, variableName, stringVariableName, boxedType, + parseTypeMethodName + ) + } + } + is StringFieldInfo -> + addStatement( + "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)", + variableName, field.attributeName + ) + else -> error(field) + } + } + if (tagFields.isNotEmpty()) { + for (field in tagFields) { + val variableName = nameAllocator.newName(field.variableName, field) + when (field) { + is ClassFieldInfo -> + addStatement("\$1T \$2L =\$Wnull", field.type, variableName) + is ListFieldInfo -> + addStatement( + "final \$1T \$2L =\$Wnew \$3T<>()", field.type, variableName, + ArrayList::class.java + ) + else -> error(field) + } + } + addStatement("int type") + addStatement("int depth") + addStatement("int innerDepth = parser.getDepth() + 1") + addControlFlow( + "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W" + + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))", + xmlPullParserType + ) { + addControlFlow( + "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType + ) { + addStatement("continue") + } + addControlFlow("switch (parser.getName())") { + for (field in tagFields) { + addControlFlow("case \$1S:", field.tagName) { + val variableName = nameAllocator.get(field) + when (field) { + is ClassFieldInfo -> { + addControlFlow("if (\$1L != null)", variableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Duplicate tag \"${field.tagName}\"" + ) + } + addStatement( + "\$1L =\$W\$2L(parser)", variableName, + field.parseMethodName + ) + addStatement("break") + } + is ListFieldInfo -> { + val elementNameAllocator = nameAllocator.clone() + val elementVariableName = elementNameAllocator.newName( + field.element.xmlName!!.toLowerCamelCase() + ) + addStatement( + "final \$1T \$2L =\$W\$3L(parser)", field.element.type, + elementVariableName, field.element.parseMethodName + ) + addStatement( + "\$1L.add(\$2L)", variableName, elementVariableName + ) + addStatement("break") + } + else -> error(field) + } + } + } + } + } + } + for (field in tagFields.filter { it is ClassFieldInfo && it.isRequired }) { + addControlFlow("if ($1L == null)", nameAllocator.get(field)) { + addStatement( + "throw new IllegalArgumentException(\$1S)", "Missing tag <${field.tagName}>" + ) + } + } + addStatement( + classField.fields.joinToString(",\$W", "return new \$1T(", ")") { + nameAllocator.get(it) + }, classField.type + ) + } + .build() + +private val ClassFieldInfo.parseMethodName: String + get() = "parse${type.simpleName().toUpperCamelCase()}" + +private val xmlSerializerType = ClassName.get("org.xmlpull.v1", "XmlSerializer") + +private fun generateWriteMethod(rootField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder("write") + .apply { + val nameAllocator = NameAllocator().apply { + newName("outputStream") + newName("serializer") + } + val parameterName = nameAllocator.newName(rootField.variableName) + addJavadoc( + """ + Write${'$'}W{@link $1T}${'$'}Wto${'$'}Wthe${'$'}WXML${'$'}Wfile. + + @param $2L the${'$'}W{@link ${'$'}1T}${'$'}Wto${'$'}Wpersist + """.trimIndent(), rootField.type, parameterName + ) + addAnnotation(nullableType) + addModifiers(Modifier.PUBLIC) + addParameter( + ParameterSpec.builder(rootField.type, parameterName) + .addAnnotation(nonNullType) + .build() + ) + addStatement("\$1T outputStream = null", FileOutputStream::class.java) + addControlFlow("try") { + addStatement("outputStream = mFile.startWrite()") + addStatement( + "final \$1T serializer =\$W\$2T.newSerializer()", xmlSerializerType, xmlType + ) + addStatement( + "serializer.setOutput(outputStream, \$1T.UTF_8.name())", + StandardCharsets::class.java + ) + addStatement( + "serializer.setFeature(\$1S, true)", + "http://xmlpull.org/v1/doc/features.html#indent-output" + ) + addStatement("serializer.startDocument(null, true)") + addStatement("serialize(serializer,\$W\$1L)", parameterName) + addStatement("serializer.endDocument()") + addStatement("mFile.finishWrite(outputStream)") + nextControlFlow("catch (Exception e)") + addStatement("e.printStackTrace()") + addStatement("mFile.failWrite(outputStream)") + } + } + .build() + +private fun generateSerializeMethod(rootField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder("serialize") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .addParameter( + ParameterSpec.builder(xmlSerializerType, "serializer") + .addAnnotation(nonNullType) + .build() + ) + .apply { + val nameAllocator = NameAllocator().apply { newName("serializer") } + val parameterName = nameAllocator.newName(rootField.variableName) + addParameter( + ParameterSpec.builder(rootField.type, parameterName) + .addAnnotation(nonNullType) + .build() + ) + addException(IOException::class.java) + addStatement("serializer.startTag(null, \$1S)", rootField.tagName) + addStatement("\$1L(serializer, \$2L)", rootField.serializeMethodName, parameterName) + addStatement("serializer.endTag(null, \$1S)", rootField.tagName) + } + .build() + +private fun generateSerializeClassMethod(classField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder(classField.serializeMethodName) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .addParameter( + ParameterSpec.builder(xmlSerializerType, "serializer") + .addAnnotation(nonNullType) + .build() + ) + .apply { + val nameAllocator = NameAllocator().apply { + newName("serializer") + newName("i") + } + val parameterName = nameAllocator.newName(classField.serializeParameterName) + addParameter( + ParameterSpec.builder(classField.type, parameterName) + .addAnnotation(nonNullType) + .build() + ) + addException(IOException::class.java) + val (attributeFields, tagFields) = classField.fields + .partition { it is PrimitiveFieldInfo || it is StringFieldInfo } + for (field in attributeFields) { + val variableName = "$parameterName.${field.name}" + if (!field.isRequired) { + beginControlFlow("if (\$1L != null)", variableName) + } + when (field) { + is PrimitiveFieldInfo -> { + if (field.isRequired && !field.type.isPrimitive) { + addControlFlow("if (\$1L == null)", variableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Field \"${field.name}\" is null" + ) + } + } + val stringVariableName = + nameAllocator.newName("${field.variableName}String") + addStatement( + "final String \$1L =\$WString.valueOf(\$2L)", stringVariableName, + variableName + ) + addStatement( + "serializer.attribute(null, \$1S, \$2L)", field.attributeName, + stringVariableName + ) + } + is StringFieldInfo -> { + if (field.isRequired) { + addControlFlow("if (\$1L == null)", variableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Field \"${field.name}\" is null" + ) + } + } + addStatement( + "serializer.attribute(null, \$1S, \$2L)", field.attributeName, + variableName + ) + } + else -> error(field) + } + if (!field.isRequired) { + endControlFlow() + } + } + for (field in tagFields) { + val variableName = "$parameterName.${field.name}" + if (field.isRequired) { + addControlFlow("if (\$1L == null)", variableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Field \"${field.name}\" is null" + ) + } + } + when (field) { + is ClassFieldInfo -> { + addStatement("serializer.startTag(null, \$1S)", field.tagName) + addStatement( + "\$1L(serializer, \$2L)", field.serializeMethodName, variableName + ) + addStatement("serializer.endTag(null, \$1S)", field.tagName) + } + is ListFieldInfo -> { + val sizeVariableName = nameAllocator.newName("${field.variableName}Size") + addStatement( + "final int \$1L =\$W\$2L.size()", sizeVariableName, variableName + ) + addControlFlow("for (int i = 0;\$Wi < \$1L;\$Wi++)", sizeVariableName) { + val elementNameAllocator = nameAllocator.clone() + val elementVariableName = elementNameAllocator.newName( + field.element.xmlName!!.toLowerCamelCase() + ) + addStatement( + "final \$1T \$2L =\$W\$3L.get(i)", field.element.type, + elementVariableName, variableName + ) + addControlFlow("if (\$1L == null)", elementVariableName) { + addStatement( + "throw new IllegalArgumentException(\$1S\$W+ i\$W+ \$2S)", + "Field element \"${field.name}[", "]\" is null" + ) + } + addStatement("serializer.startTag(null, \$1S)", field.element.tagName) + addStatement( + "\$1L(serializer,\$W\$2L)", field.element.serializeMethodName, + elementVariableName + ) + addStatement("serializer.endTag(null, \$1S)", field.element.tagName) + } + } + else -> error(field) + } + } + } + .build() + +private val ClassFieldInfo.serializeMethodName: String + get() = "serialize${type.simpleName().toUpperCamelCase()}" + +private val ClassFieldInfo.serializeParameterName: String + get() = type.simpleName().toLowerCamelCase() + +private val FieldInfo.variableName: String + get() = name.toLowerCamelCase() + +private val FieldInfo.attributeName: String + get() { + check(this is PrimitiveFieldInfo || this is StringFieldInfo) + return xmlNameOrName.toLowerCamelCase() + } + +private val FieldInfo.tagName: String + get() { + check(this is ClassFieldInfo || this is ListFieldInfo) + return xmlNameOrName.toLowerKebabCase() + } + +private val FieldInfo.xmlNameOrName: String + get() = xmlName ?: name + +private fun generateDeleteMethod(): MethodSpec = + MethodSpec.methodBuilder("delete") + .addJavadoc("Delete the XML file, if any.") + .addModifiers(Modifier.PUBLIC) + .addStatement("mFile.delete()") + .build() + +private inline fun MethodSpec.Builder.addControlFlow( + controlFlow: String, + vararg args: Any, + block: MethodSpec.Builder.() -> Unit +): MethodSpec.Builder { + beginControlFlow(controlFlow, *args) + block() + endControlFlow() + return this +} diff --git a/tools/xmlpersistence/src/main/kotlin/Main.kt b/tools/xmlpersistence/src/main/kotlin/Main.kt new file mode 100644 index 000000000000..e271f8cb9361 --- /dev/null +++ b/tools/xmlpersistence/src/main/kotlin/Main.kt @@ -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. + */ + +import java.io.File +import java.nio.file.Files + +fun main(args: Array<String>) { + val showUsage = args.isEmpty() || when (args.singleOrNull()) { + "-h", "--help" -> true + else -> false + } + if (showUsage) { + usage() + return + } + + val files = args.flatMap { + File(it).walk().filter { it.isFile && it.extension == "java" }.map { it.toPath() } + } + val persistences = parse(files) + for (persistence in persistences) { + val file = generate(persistence) + Files.newBufferedWriter(persistence.path).use { + it.write(FILE_HEADER) + file.writeTo(it) + } + } +} + +private fun usage() { + println("Usage: xmlpersistence <FILES>") +} diff --git a/tools/xmlpersistence/src/main/kotlin/Parser.kt b/tools/xmlpersistence/src/main/kotlin/Parser.kt new file mode 100644 index 000000000000..3ea12a9aa389 --- /dev/null +++ b/tools/xmlpersistence/src/main/kotlin/Parser.kt @@ -0,0 +1,248 @@ +/* + * 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. + */ + +import com.github.javaparser.JavaParser +import com.github.javaparser.ParseProblemException +import com.github.javaparser.ParseResult +import com.github.javaparser.ParserConfiguration +import com.github.javaparser.ast.Node +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration +import com.github.javaparser.ast.body.FieldDeclaration +import com.github.javaparser.ast.body.TypeDeclaration +import com.github.javaparser.ast.expr.AnnotationExpr +import com.github.javaparser.ast.expr.Expression +import com.github.javaparser.ast.expr.NormalAnnotationExpr +import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr +import com.github.javaparser.ast.expr.StringLiteralExpr +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration +import com.github.javaparser.resolution.types.ResolvedPrimitiveType +import com.github.javaparser.resolution.types.ResolvedReferenceType +import com.github.javaparser.symbolsolver.JavaSymbolSolver +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration +import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver +import com.github.javaparser.symbolsolver.resolution.typesolvers.MemoryTypeSolver +import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.ParameterizedTypeName +import com.squareup.javapoet.TypeName +import java.nio.file.Path +import java.util.Optional + +class PersistenceInfo( + val name: String, + val root: ClassFieldInfo, + val path: Path +) + +sealed class FieldInfo { + abstract val name: String + abstract val xmlName: String? + abstract val type: TypeName + abstract val isRequired: Boolean +} + +class PrimitiveFieldInfo( + override val name: String, + override val xmlName: String?, + override val type: TypeName, + override val isRequired: Boolean +) : FieldInfo() + +class StringFieldInfo( + override val name: String, + override val xmlName: String?, + override val isRequired: Boolean +) : FieldInfo() { + override val type: TypeName = ClassName.get(String::class.java) +} + +class ClassFieldInfo( + override val name: String, + override val xmlName: String?, + override val type: ClassName, + override val isRequired: Boolean, + val fields: List<FieldInfo> +) : FieldInfo() + +class ListFieldInfo( + override val name: String, + override val xmlName: String?, + override val type: ParameterizedTypeName, + val element: ClassFieldInfo +) : FieldInfo() { + override val isRequired: Boolean = true +} + +fun parse(files: List<Path>): List<PersistenceInfo> { + val typeSolver = CombinedTypeSolver().apply { add(ReflectionTypeSolver()) } + val javaParser = JavaParser(ParserConfiguration() + .setSymbolResolver(JavaSymbolSolver(typeSolver))) + val compilationUnits = files.map { javaParser.parse(it).getOrThrow() } + val memoryTypeSolver = MemoryTypeSolver().apply { + for (compilationUnit in compilationUnits) { + for (typeDeclaration in compilationUnit.getNodesByClass<TypeDeclaration<*>>()) { + val name = typeDeclaration.fullyQualifiedName.getOrNull() ?: continue + addDeclaration(name, typeDeclaration.resolve()) + } + } + } + typeSolver.add(memoryTypeSolver) + return mutableListOf<PersistenceInfo>().apply { + for (compilationUnit in compilationUnits) { + val classDeclarations = compilationUnit + .getNodesByClass<ClassOrInterfaceDeclaration>() + .filter { !it.isInterface && (!it.isNestedType || it.isStatic) } + this += classDeclarations.mapNotNull { parsePersistenceInfo(it) } + } + } +} + +private fun parsePersistenceInfo(classDeclaration: ClassOrInterfaceDeclaration): PersistenceInfo? { + val annotation = classDeclaration.getAnnotationByName("XmlPersistence").getOrNull() + ?: return null + val rootClassName = classDeclaration.nameAsString + val name = annotation.getMemberValue("value")?.stringLiteralValue + ?: "${rootClassName}Persistence" + val rootXmlName = classDeclaration.getAnnotationByName("XmlName").getOrNull() + ?.getMemberValue("value")?.stringLiteralValue + val root = parseClassFieldInfo( + rootXmlName ?: rootClassName, rootXmlName, true, classDeclaration + ) + val path = classDeclaration.findCompilationUnit().get().storage.get().path + .resolveSibling("$name.java") + return PersistenceInfo(name, root, path) +} + +private fun parseClassFieldInfo( + name: String, + xmlName: String?, + isRequired: Boolean, + classDeclaration: ClassOrInterfaceDeclaration +): ClassFieldInfo { + val fields = classDeclaration.fields.filterNot { it.isStatic }.map { parseFieldInfo(it) } + val type = classDeclaration.resolve().typeName + return ClassFieldInfo(name, xmlName, type, isRequired, fields) +} + +private fun parseFieldInfo(field: FieldDeclaration): FieldInfo { + require(field.isPublic && field.isFinal) + val variable = field.variables.single() + val name = variable.nameAsString + val annotations = field.annotations + variable.type.annotations + val annotation = annotations.getByName("XmlName") + val xmlName = annotation?.getMemberValue("value")?.stringLiteralValue + val isRequired = annotations.getByName("NonNull") != null + return when (val type = variable.type.resolve()) { + is ResolvedPrimitiveType -> { + val primitiveType = type.typeName + PrimitiveFieldInfo(name, xmlName, primitiveType, true) + } + is ResolvedReferenceType -> { + when (type.qualifiedName) { + Boolean::class.javaObjectType.name, Byte::class.javaObjectType.name, + Short::class.javaObjectType.name, Char::class.javaObjectType.name, + Integer::class.javaObjectType.name, Long::class.javaObjectType.name, + Float::class.javaObjectType.name, Double::class.javaObjectType.name -> + PrimitiveFieldInfo(name, xmlName, type.typeName, isRequired) + String::class.java.name -> StringFieldInfo(name, xmlName, isRequired) + List::class.java.name -> { + requireNotNull(xmlName) + val elementType = type.typeParametersValues().single() + require(elementType is ResolvedReferenceType) + val listType = ParameterizedTypeName.get( + ClassName.get(List::class.java), elementType.typeName + ) + val element = parseClassFieldInfo( + "(element)", xmlName, true, elementType.classDeclaration + ) + ListFieldInfo(name, xmlName, listType, element) + } + else -> parseClassFieldInfo(name, xmlName, isRequired, type.classDeclaration) + } + } + else -> error(type) + } +} + +private fun <T> ParseResult<T>.getOrThrow(): T = + if (isSuccessful) { + result.get() + } else { + throw ParseProblemException(problems) + } + +private inline fun <reified T : Node> Node.getNodesByClass(): List<T> = + getNodesByClass(T::class.java) + +private fun <T : Node> Node.getNodesByClass(klass: Class<T>): List<T> = mutableListOf<T>().apply { + if (klass.isInstance(this@getNodesByClass)) { + this += klass.cast(this@getNodesByClass) + } + for (childNode in childNodes) { + this += childNode.getNodesByClass(klass) + } +} + +private fun <T> Optional<T>.getOrNull(): T? = orElse(null) + +private fun List<AnnotationExpr>.getByName(name: String): AnnotationExpr? = + find { it.name.identifier == name } + +private fun AnnotationExpr.getMemberValue(name: String): Expression? = + when (this) { + is NormalAnnotationExpr -> pairs.find { it.nameAsString == name }?.value + is SingleMemberAnnotationExpr -> if (name == "value") memberValue else null + else -> null + } + +private val Expression.stringLiteralValue: String + get() { + require(this is StringLiteralExpr) + return value + } + +private val ResolvedReferenceType.classDeclaration: ClassOrInterfaceDeclaration + get() { + val resolvedClassDeclaration = typeDeclaration + require(resolvedClassDeclaration is JavaParserClassDeclaration) + return resolvedClassDeclaration.wrappedNode + } + +private val ResolvedPrimitiveType.typeName: TypeName + get() = + when (this) { + ResolvedPrimitiveType.BOOLEAN -> TypeName.BOOLEAN + ResolvedPrimitiveType.BYTE -> TypeName.BYTE + ResolvedPrimitiveType.SHORT -> TypeName.SHORT + ResolvedPrimitiveType.CHAR -> TypeName.CHAR + ResolvedPrimitiveType.INT -> TypeName.INT + ResolvedPrimitiveType.LONG -> TypeName.LONG + ResolvedPrimitiveType.FLOAT -> TypeName.FLOAT + ResolvedPrimitiveType.DOUBLE -> TypeName.DOUBLE + } + +// This doesn't support type parameters. +private val ResolvedReferenceType.typeName: TypeName + get() = typeDeclaration.typeName + +private val ResolvedReferenceTypeDeclaration.typeName: ClassName + get() { + val packageName = packageName + val classNames = className.split(".") + val topLevelClassName = classNames.first() + val nestedClassNames = classNames.drop(1) + return ClassName.get(packageName, topLevelClassName, *nestedClassNames.toTypedArray()) + } diff --git a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt new file mode 100644 index 000000000000..b4bdbba7170b --- /dev/null +++ b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt @@ -0,0 +1,44 @@ +/* + * 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. + */ + +import java.util.Locale + +private val camelHumpBoundary = Regex( + "-" + + "|_" + + "|(?<=[0-9])(?=[^0-9])" + + "|(?<=[A-Z])(?=[^A-Za-z]|[A-Z][a-z])" + + "|(?<=[a-z])(?=[^a-z])" +) + +private fun String.toCamelHumps(): List<String> = split(camelHumpBoundary) + +fun String.toUpperCamelCase(): String = + toCamelHumps().joinToString("") { it.toLowerCase(Locale.ROOT).capitalize(Locale.ROOT) } + +fun String.toLowerCamelCase(): String = toUpperCamelCase().decapitalize(Locale.ROOT) + +fun String.toUpperKebabCase(): String = + toCamelHumps().joinToString("-") { it.toUpperCase(Locale.ROOT) } + +fun String.toLowerKebabCase(): String = + toCamelHumps().joinToString("-") { it.toLowerCase(Locale.ROOT) } + +fun String.toUpperSnakeCase(): String = + toCamelHumps().joinToString("_") { it.toUpperCase(Locale.ROOT) } + +fun String.toLowerSnakeCase(): String = + toCamelHumps().joinToString("_") { it.toLowerCase(Locale.ROOT) } diff --git a/wifi/api/current.txt b/wifi/api/current.txt index 1c297e7058dd..69286187eef3 100644 --- a/wifi/api/current.txt +++ b/wifi/api/current.txt @@ -332,6 +332,7 @@ package android.net.wifi { method public boolean isEasyConnectSupported(); method public boolean isEnhancedOpenSupported(); method public boolean isEnhancedPowerReportingSupported(); + method public boolean isMultiStaConcurrencySupported(); method public boolean isP2pSupported(); method public boolean isPreferredNetworkOffloadSupported(); method @Deprecated public boolean isScanAlwaysAvailable(); diff --git a/wifi/api/system-current.txt b/wifi/api/system-current.txt index fd45ebec80ef..bf7003c409f9 100644 --- a/wifi/api/system-current.txt +++ b/wifi/api/system-current.txt @@ -509,7 +509,7 @@ package android.net.wifi { field public static final int EASY_CONNECT_NETWORK_ROLE_STA = 0; // 0x0 field public static final String EXTRA_CHANGE_REASON = "changeReason"; field @Deprecated public static final String EXTRA_LINK_PROPERTIES = "android.net.wifi.extra.LINK_PROPERTIES"; - field public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges"; + field @Deprecated public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges"; field public static final String EXTRA_OSU_NETWORK = "android.net.wifi.extra.OSU_NETWORK"; field public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state"; field public static final String EXTRA_URL = "android.net.wifi.extra.URL"; @@ -517,7 +517,7 @@ package android.net.wifi { field public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME"; field public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE"; field public static final String EXTRA_WIFI_AP_STATE = "wifi_state"; - field public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration"; + field @Deprecated public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration"; field public static final String EXTRA_WIFI_CREDENTIAL_EVENT_TYPE = "et"; field public static final String EXTRA_WIFI_CREDENTIAL_SSID = "ssid"; field public static final int IFACE_IP_MODE_CONFIGURATION_ERROR = 0; // 0x0 @@ -608,10 +608,12 @@ package android.net.wifi { public final class WifiNetworkSuggestion implements android.os.Parcelable { method @NonNull public android.net.wifi.WifiConfiguration getWifiConfiguration(); + method public boolean isOemPaid(); } public static final class WifiNetworkSuggestion.Builder { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int); + method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setOemPaid(boolean); } public class WifiScanner { diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index bc837b3c344d..237922a6fe1e 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -33,7 +33,6 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -767,10 +766,6 @@ public final class SoftApConfiguration implements Parcelable { } } else { Preconditions.checkStringNotEmpty(passphrase); - final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); - if (!asciiEncoder.canEncode(passphrase)) { - throw new IllegalArgumentException("passphrase not ASCII encodable"); - } if (securityType == SECURITY_TYPE_WPA2_PSK || securityType == SECURITY_TYPE_WPA3_SAE_TRANSITION) { if (passphrase.length() < PSK_MIN_LEN || passphrase.length() > PSK_MAX_LEN) { diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 391be357e963..a4f802a28cb7 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -29,7 +29,6 @@ import android.net.NetworkSpecifier; import android.net.ProxyInfo; import android.net.StaticIpConfiguration; import android.net.Uri; -import android.net.util.MacAddressUtils; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -41,6 +40,7 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.MacAddressUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -992,6 +992,16 @@ public class WifiConfiguration implements Parcelable { public boolean trusted; /** + * Indicate whether the network is oem paid or not. Networks are considered oem paid + * if the corresponding connection is only available to system apps. + * + * This bit can only be used by suggestion network, see + * {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)} + * @hide + */ + public boolean oemPaid; + + /** * True if this Wifi configuration is created from a {@link WifiNetworkSuggestion}, * false otherwise. * @@ -2158,6 +2168,7 @@ public class WifiConfiguration implements Parcelable { ephemeral = false; osu = false; trusted = true; // Networks are considered trusted by default. + oemPaid = false; fromWifiNetworkSuggestion = false; fromWifiNetworkSpecifier = false; meteredHint = false; @@ -2278,11 +2289,12 @@ public class WifiConfiguration implements Parcelable { if (this.ephemeral) sbuf.append(" ephemeral"); if (this.osu) sbuf.append(" osu"); if (this.trusted) sbuf.append(" trusted"); + if (this.oemPaid) sbuf.append(" oemPaid"); if (this.fromWifiNetworkSuggestion) sbuf.append(" fromWifiNetworkSuggestion"); if (this.fromWifiNetworkSpecifier) sbuf.append(" fromWifiNetworkSpecifier"); if (this.meteredHint) sbuf.append(" meteredHint"); if (this.useExternalScores) sbuf.append(" useExternalScores"); - if (this.validatedInternetAccess || this.ephemeral || this.trusted + if (this.validatedInternetAccess || this.ephemeral || this.trusted || this.oemPaid || this.fromWifiNetworkSuggestion || this.fromWifiNetworkSpecifier || this.meteredHint || this.useExternalScores) { sbuf.append("\n"); @@ -2828,6 +2840,7 @@ public class WifiConfiguration implements Parcelable { ephemeral = source.ephemeral; osu = source.osu; trusted = source.trusted; + oemPaid = source.oemPaid; fromWifiNetworkSuggestion = source.fromWifiNetworkSuggestion; fromWifiNetworkSpecifier = source.fromWifiNetworkSpecifier; meteredHint = source.meteredHint; @@ -2906,6 +2919,7 @@ public class WifiConfiguration implements Parcelable { dest.writeInt(isLegacyPasspointConfig ? 1 : 0); dest.writeInt(ephemeral ? 1 : 0); dest.writeInt(trusted ? 1 : 0); + dest.writeInt(oemPaid ? 1 : 0); dest.writeInt(fromWifiNetworkSuggestion ? 1 : 0); dest.writeInt(fromWifiNetworkSpecifier ? 1 : 0); dest.writeInt(meteredHint ? 1 : 0); @@ -2981,6 +2995,7 @@ public class WifiConfiguration implements Parcelable { config.isLegacyPasspointConfig = in.readInt() != 0; config.ephemeral = in.readInt() != 0; config.trusted = in.readInt() != 0; + config.oemPaid = in.readInt() != 0; config.fromWifiNetworkSuggestion = in.readInt() != 0; config.fromWifiNetworkSpecifier = in.readInt() != 0; config.meteredHint = in.readInt() != 0; diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 53883674e058..fe5002eb8854 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -159,6 +159,11 @@ public class WifiInfo implements Parcelable { private boolean mTrusted; /** + * Whether the network is oem paid or not. + */ + private boolean mOemPaid; + + /** * OSU (Online Sign Up) AP for Passpoint R2. */ private boolean mOsuAp; @@ -358,6 +363,7 @@ public class WifiInfo implements Parcelable { mMeteredHint = source.mMeteredHint; mEphemeral = source.mEphemeral; mTrusted = source.mTrusted; + mTrusted = source.mOemPaid; mRequestingPackageName = source.mRequestingPackageName; mOsuAp = source.mOsuAp; @@ -722,6 +728,16 @@ public class WifiInfo implements Parcelable { } /** {@hide} */ + public void setOemPaid(boolean oemPaid) { + mOemPaid = oemPaid; + } + + /** {@hide} */ + public boolean isOemPaid() { + return mOemPaid; + } + + /** {@hide} */ public void setOsuAp(boolean osuAp) { mOsuAp = osuAp; } @@ -958,6 +974,7 @@ public class WifiInfo implements Parcelable { dest.writeInt(mMeteredHint ? 1 : 0); dest.writeInt(mEphemeral ? 1 : 0); dest.writeInt(mTrusted ? 1 : 0); + dest.writeInt(mOemPaid ? 1 : 0); dest.writeInt(score); dest.writeLong(txSuccess); dest.writeDouble(mSuccessfulTxPacketsPerSecond); @@ -1003,6 +1020,7 @@ public class WifiInfo implements Parcelable { info.mMeteredHint = in.readInt() != 0; info.mEphemeral = in.readInt() != 0; info.mTrusted = in.readInt() != 0; + info.mOemPaid = in.readInt() != 0; info.score = in.readInt(); info.txSuccess = in.readLong(); info.mSuccessfulTxPacketsPerSecond = in.readDouble(); diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 2219bfcd6aab..2ca2d1e39049 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -44,6 +44,7 @@ import android.net.wifi.hotspot2.IProvisioningCallback; import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; import android.net.wifi.hotspot2.ProvisioningCallback; +import android.net.wifi.util.SdkLevelUtil; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -931,9 +932,11 @@ public class WifiManager { * This can be as a result of adding/updating/deleting a network. * <br /> * {@link #EXTRA_CHANGE_REASON} contains whether the configuration was added/changed/removed. - * {@link #EXTRA_WIFI_CONFIGURATION} is never set starting in Android 11. + * {@link #EXTRA_WIFI_CONFIGURATION} is never set beginning in + * {@link android.os.Build.VERSION_CODES#R}. * {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is set for backwards compatibility reasons, but - * its value is always true, even if only a single network changed. + * its value is always true beginning in {@link android.os.Build.VERSION_CODES#R}, even if only + * a single network changed. * <br /> * The {@link android.Manifest.permission#ACCESS_WIFI_STATE ACCESS_WIFI_STATE} permission is * required to receive this broadcast. @@ -947,17 +950,22 @@ public class WifiManager { * The lookup key for a {@link android.net.wifi.WifiConfiguration} object representing * the changed Wi-Fi configuration when the {@link #CONFIGURED_NETWORKS_CHANGED_ACTION} * broadcast is sent. - * Note: this extra is never set starting in Android 11. + * @deprecated This extra is never set beginning in {@link android.os.Build.VERSION_CODES#R}, + * regardless of the target SDK version. Use {@link #getConfiguredNetworks} to get the full list + * of configured networks. * @hide */ + @Deprecated @SystemApi public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration"; /** * Multiple network configurations have changed. * @see #CONFIGURED_NETWORKS_CHANGED_ACTION - * Note: this extra is always true starting in Android 11. + * @deprecated This extra's value is always true beginning in + * {@link android.os.Build.VERSION_CODES#R}, regardless of the target SDK version. * @hide */ + @Deprecated @SystemApi public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges"; /** @@ -2480,6 +2488,18 @@ public class WifiManager { } /** + * Query whether the device supports 2 or more concurrent stations (STA) or not. + * + * @return true if this device supports multiple STA concurrency, false otherwise. + */ + public boolean isMultiStaConcurrencySupported() { + if (!SdkLevelUtil.isAtLeastS()) { + throw new UnsupportedOperationException(); + } + return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA); + } + + /** * @deprecated Please use {@link android.content.pm.PackageManager#hasSystemFeature(String)} * with {@link android.content.pm.PackageManager#FEATURE_WIFI_RTT} and * {@link android.content.pm.PackageManager#FEATURE_WIFI_AWARE}. @@ -2512,14 +2532,6 @@ public class WifiManager { } /** - * @return true if this adapter supports multiple simultaneous connections - * @hide - */ - public boolean isAdditionalStaSupported() { - return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA); - } - - /** * @return true if this adapter supports Tunnel Directed Link Setup */ public boolean isTdlsSupported() { diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java index e992c830aa87..9162c5f3e0d9 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -24,7 +24,9 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.net.MacAddress; +import android.net.NetworkCapabilities; import android.net.wifi.hotspot2.PasspointConfiguration; +import android.net.wifi.util.SdkLevelUtil; import android.os.Parcel; import android.os.Parcelable; import android.telephony.TelephonyManager; @@ -150,6 +152,11 @@ public final class WifiNetworkSuggestion implements Parcelable { private boolean mIsNetworkUntrusted; /** + * Whether this network will be brought up as OEM paid (OEM_PAID capability bit added). + */ + private boolean mIsNetworkOemPaid; + + /** * Whether this network will use enhanced MAC randomization. */ private boolean mIsEnhancedMacRandomizationEnabled; @@ -175,6 +182,7 @@ public final class WifiNetworkSuggestion implements Parcelable { mWapiPskPassphrase = null; mWapiEnterpriseConfig = null; mIsNetworkUntrusted = false; + mIsNetworkOemPaid = false; mPriorityGroup = 0; mIsEnhancedMacRandomizationEnabled = false; } @@ -543,7 +551,7 @@ public final class WifiNetworkSuggestion implements Parcelable { /** * Specifies whether the system will bring up the network (if selected) as untrusted. An - * untrusted network has its {@link android.net.NetworkCapabilities#NET_CAPABILITY_TRUSTED} + * untrusted network has its {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} * capability removed. The Wi-Fi network selection process may use this information to * influence priority of the suggested network for Wi-Fi network selection (most likely to * reduce it). The connectivity service may use this information to influence the overall @@ -562,6 +570,41 @@ public final class WifiNetworkSuggestion implements Parcelable { return this; } + /** + * Specifies whether the system will bring up the network (if selected) as OEM paid. An + * OEM paid network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID} capability + * added. + * Note: + * <li>The connectivity service may use this information to influence the overall + * network configuration of the device. This network is typically only available to system + * apps. + * <li>On devices which support only 1 concurrent connection (indicated via + * {@link WifiManager#isMultiStaConcurrencySupported()}, Wi-Fi network selection process may + * use this information to influence priority of the suggested network for Wi-Fi network + * selection (most likely to reduce it). + * <li>On devices which support more than 1 concurrent connections (indicated via + * {@link WifiManager#isMultiStaConcurrencySupported()}, these OEM paid networks will be + * brought up as a secondary concurrent connection (primary connection will be used + * for networks available to the user and all apps. + * <p> + * <li> An OEM paid network's credentials may not be shared with the user using + * {@link #setCredentialSharedWithUser(boolean)}.</li> + * <li> If not set, defaults to false (i.e. network is not OEM paid).</li> + * + * @param isOemPaid Boolean indicating whether the network should be brought up as OEM paid + * (if true) or not OEM paid (if false). + * @return Instance of {@link Builder} to enable chaining of the builder method. + * @hide + */ + @SystemApi + public @NonNull Builder setOemPaid(boolean isOemPaid) { + if (!SdkLevelUtil.isAtLeastS()) { + throw new UnsupportedOperationException(); + } + mIsNetworkOemPaid = isOemPaid; + return this; + } + private void setSecurityParamsInWifiConfiguration( @NonNull WifiConfiguration configuration) { if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network. @@ -628,6 +671,7 @@ public final class WifiNetworkSuggestion implements Parcelable { wifiConfiguration.meteredOverride = mMeteredOverride; wifiConfiguration.carrierId = mCarrierId; wifiConfiguration.trusted = !mIsNetworkUntrusted; + wifiConfiguration.oemPaid = mIsNetworkOemPaid; wifiConfiguration.macRandomizationSetting = mIsEnhancedMacRandomizationEnabled ? WifiConfiguration.RANDOMIZATION_ENHANCED : WifiConfiguration.RANDOMIZATION_PERSISTENT; @@ -659,6 +703,7 @@ public final class WifiNetworkSuggestion implements Parcelable { wifiConfiguration.priority = mPriority; wifiConfiguration.meteredOverride = mMeteredOverride; wifiConfiguration.trusted = !mIsNetworkUntrusted; + wifiConfiguration.oemPaid = mIsNetworkOemPaid; mPasspointConfiguration.setCarrierId(mCarrierId); mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride); wifiConfiguration.macRandomizationSetting = mIsEnhancedMacRandomizationEnabled @@ -764,7 +809,15 @@ public final class WifiNetworkSuggestion implements Parcelable { if (mIsSharedWithUserSet && mIsSharedWithUser) { throw new IllegalStateException("Should not be both" + "setCredentialSharedWithUser and +" - + "setIsNetworkAsUntrusted to true"); + + "setUntrusted to true"); + } + mIsSharedWithUser = false; + } + if (mIsNetworkOemPaid) { + if (mIsSharedWithUserSet && mIsSharedWithUser) { + throw new IllegalStateException("Should not be both" + + "setCredentialSharedWithUser and +" + + "setOemPaid to true"); } mIsSharedWithUser = false; } @@ -931,6 +984,7 @@ public final class WifiNetworkSuggestion implements Parcelable { .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect) .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled) .append(", isUnTrusted=").append(!wifiConfiguration.trusted) + .append(", isOemPaid=").append(wifiConfiguration.oemPaid) .append(", priorityGroup=").append(priorityGroup) .append(" ]"); return sb.toString(); @@ -1026,6 +1080,18 @@ public final class WifiNetworkSuggestion implements Parcelable { } /** + * @see Builder#setOemPaid(boolean) + * @hide + */ + @SystemApi + public boolean isOemPaid() { + if (!SdkLevelUtil.isAtLeastS()) { + throw new UnsupportedOperationException(); + } + return wifiConfiguration.oemPaid; + } + + /** * Get the WifiEnterpriseConfig, or null if unset. * @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig) * @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig) diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java index dad431c1ca2c..e2f40cfa058c 100644 --- a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java @@ -17,10 +17,11 @@ package android.net.wifi.p2p.nsd; import android.compat.annotation.UnsupportedAppUsage; -import android.net.util.nsd.DnsSdTxtRecord; import android.os.Build; import android.text.TextUtils; +import com.android.net.module.util.DnsSdTxtRecord; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; diff --git a/wifi/java/android/net/wifi/util/SdkLevelUtil.java b/wifi/java/android/net/wifi/util/SdkLevelUtil.java index 042634c7125c..d08d4fd742b7 100644 --- a/wifi/java/android/net/wifi/util/SdkLevelUtil.java +++ b/wifi/java/android/net/wifi/util/SdkLevelUtil.java @@ -23,17 +23,17 @@ import android.os.Build; * * This can be used to disable new Wifi APIs added in Mainline updates on older SDK versions. * + * Note: if certain functionality is gated with SdkLevelUtil, its corresponding unit tests should + * also be gated by the same condition. Then, those unit tests will only be exercised on a base + * system image satisfying that condition. + * Alternatively, it can be tested via static mocking. + * * @hide */ public class SdkLevelUtil { - /** This class is instantiable to allow easy mocking. */ - public SdkLevelUtil() { } - - /** See {@link #isAtLeastS()}. This version is non-static to allow easy mocking. */ - public boolean isAtLeastSMockable() { - return isAtLeastS(); - } + /** This class is not instantiable. */ + private SdkLevelUtil() {} /** Returns true if the Android platform SDK is at least "S", false otherwise. */ public static boolean isAtLeastS() { diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index 62220a6237b1..6894ba074f51 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -33,13 +33,14 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import android.net.MacAddress; -import android.net.util.MacAddressUtils; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; import android.os.Parcel; import androidx.test.filters.SmallTest; +import com.android.net.module.util.MacAddressUtils; + import org.junit.Before; import org.junit.Test; @@ -67,6 +68,7 @@ public class WifiConfigurationTest { WifiConfiguration config = new WifiConfiguration(); config.setPasspointManagementObjectTree(cookie); config.trusted = false; + config.oemPaid = true; config.updateIdentifier = "1234"; config.fromWifiNetworkSpecifier = true; config.fromWifiNetworkSuggestion = true; diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java index 311bbc41b8fe..06ae13a21e38 100644 --- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java +++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java @@ -61,6 +61,7 @@ public class WifiInfoTest { writeWifiInfo.txBad = TEST_TX_BAD; writeWifiInfo.rxSuccess = TEST_RX_SUCCESS; writeWifiInfo.setTrusted(true); + writeWifiInfo.setOemPaid(true); writeWifiInfo.setOsuAp(true); writeWifiInfo.setFQDN(TEST_FQDN); writeWifiInfo.setProviderFriendlyName(TEST_PROVIDER_NAME); @@ -81,6 +82,7 @@ public class WifiInfoTest { assertEquals(TEST_TX_BAD, readWifiInfo.txBad); assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess); assertTrue(readWifiInfo.isTrusted()); + assertTrue(readWifiInfo.isOemPaid()); assertTrue(readWifiInfo.isOsuAp()); assertTrue(readWifiInfo.isPasspointAp()); assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getRequestingPackageName()); diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index e7f1916c9e82..bda776db733c 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -33,6 +33,8 @@ import static android.net.wifi.WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_ import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING; import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; +import static android.net.wifi.WifiManager.WIFI_FEATURE_ADDITIONAL_STA; +import static android.net.wifi.WifiManager.WIFI_FEATURE_AP_STA; import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP; import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE; import static android.net.wifi.WifiManager.WIFI_FEATURE_P2P; @@ -49,6 +51,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.any; @@ -83,6 +86,7 @@ import android.net.wifi.WifiManager.SoftApCallback; import android.net.wifi.WifiManager.SuggestionConnectionStatusListener; import android.net.wifi.WifiManager.TrafficStateCallback; import android.net.wifi.WifiManager.WifiConnectedNetworkScorer; +import android.net.wifi.util.SdkLevelUtil; import android.os.Build; import android.os.Handler; import android.os.HandlerExecutor; @@ -1708,6 +1712,34 @@ public class WifiManagerTest { } /** + * Test behavior of isStaApConcurrencySupported + */ + @Test + public void testIsStaApConcurrencyOpenSupported() throws Exception { + when(mWifiService.getSupportedFeatures()) + .thenReturn(new Long(WIFI_FEATURE_AP_STA)); + assertTrue(mWifiManager.isStaApConcurrencySupported()); + when(mWifiService.getSupportedFeatures()) + .thenReturn(new Long(~WIFI_FEATURE_AP_STA)); + assertFalse(mWifiManager.isStaApConcurrencySupported()); + } + + /** + * Test behavior of isMultiStaConcurrencySupported + */ + @Test + public void testIsMultiStaConcurrencyOpenSupported() throws Exception { + assumeTrue(SdkLevelUtil.isAtLeastS()); + + when(mWifiService.getSupportedFeatures()) + .thenReturn(new Long(WIFI_FEATURE_ADDITIONAL_STA)); + assertTrue(mWifiManager.isMultiStaConcurrencySupported()); + when(mWifiService.getSupportedFeatures()) + .thenReturn(new Long(~WIFI_FEATURE_ADDITIONAL_STA)); + assertFalse(mWifiManager.isMultiStaConcurrencySupported()); + } + + /** * Test behavior of {@link WifiManager#addNetwork(WifiConfiguration)} */ @Test @@ -1858,7 +1890,6 @@ public class WifiManagerTest { assertFalse(mWifiManager.isDeviceToDeviceRttSupported()); assertFalse(mWifiManager.isDeviceToApRttSupported()); assertFalse(mWifiManager.isPreferredNetworkOffloadSupported()); - assertFalse(mWifiManager.isAdditionalStaSupported()); assertFalse(mWifiManager.isTdlsSupported()); assertFalse(mWifiManager.isOffChannelTdlsSupported()); assertFalse(mWifiManager.isEnhancedPowerReportingSupported()); diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java index 668d2385a4ae..6e08ca42f25f 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java @@ -22,10 +22,12 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import android.net.MacAddress; import android.net.wifi.hotspot2.PasspointConfiguration; import android.net.wifi.hotspot2.PasspointTestUtils; +import android.net.wifi.util.SdkLevelUtil; import android.os.Parcel; import androidx.test.filters.SmallTest; @@ -192,6 +194,34 @@ public class WifiNetworkSuggestionTest { /** * Validate correctness of WifiNetworkSuggestion object created by + * {@link WifiNetworkSuggestion.Builder#build()} for OWE network. + */ + @Test + public void testWifiNetworkSuggestionBuilderForOemPaidEnhancedOpenNetworkWithBssid() { + assumeTrue(SdkLevelUtil.isAtLeastS()); + + WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder() + .setSsid(TEST_SSID) + .setBssid(MacAddress.fromString(TEST_BSSID)) + .setOemPaid(true) + .setIsEnhancedOpen(true) + .build(); + + assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID); + assertEquals(TEST_BSSID, suggestion.wifiConfiguration.BSSID); + assertTrue(suggestion.wifiConfiguration.allowedKeyManagement + .get(WifiConfiguration.KeyMgmt.OWE)); + assertNull(suggestion.wifiConfiguration.preSharedKey); + assertTrue(suggestion.wifiConfiguration.requirePmf); + assertTrue(suggestion.wifiConfiguration.oemPaid); + assertTrue(suggestion.isOemPaid()); + assertFalse(suggestion.isUserAllowedToManuallyConnect); + assertTrue(suggestion.isInitialAutoJoinEnabled); + assertNull(suggestion.getEnterpriseConfig()); + } + + /** + * Validate correctness of WifiNetworkSuggestion object created by * {@link WifiNetworkSuggestion.Builder#build()} for SAE network. */ @Test @@ -1010,6 +1040,41 @@ public class WifiNetworkSuggestionTest { } /** + * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the + * correct value to the WifiConfiguration. + */ + @Test + public void testSetIsNetworkAsOemPaid() { + assumeTrue(SdkLevelUtil.isAtLeastS()); + + WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder() + .setSsid(TEST_SSID) + .setWpa2Passphrase(TEST_PRESHARED_KEY) + .setOemPaid(true) + .build(); + assertTrue(suggestion.isOemPaid()); + assertFalse(suggestion.isUserAllowedToManuallyConnect); + } + + /** + * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the + * correct value to the WifiConfiguration. + * Also the {@link WifiNetworkSuggestion#isUserAllowedToManuallyConnect} should be false; + */ + @Test + public void testSetIsNetworkAsOemPaidOnPasspointNetwork() { + assumeTrue(SdkLevelUtil.isAtLeastS()); + + PasspointConfiguration passpointConfiguration = PasspointTestUtils.createConfig(); + WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder() + .setPasspointConfig(passpointConfiguration) + .setOemPaid(true) + .build(); + assertTrue(suggestion.isOemPaid()); + assertFalse(suggestion.isUserAllowedToManuallyConnect); + } + + /** * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception * when set {@link WifiNetworkSuggestion.Builder#setUntrusted(boolean)} to true and * set {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to true @@ -1027,6 +1092,24 @@ public class WifiNetworkSuggestionTest { /** * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception + * when set {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)} to true and + * set {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to true + * together. + */ + @Test(expected = IllegalStateException.class) + public void testSetCredentialSharedWithUserWithSetIsNetworkAsOemPaid() { + assumeTrue(SdkLevelUtil.isAtLeastS()); + + new WifiNetworkSuggestion.Builder() + .setSsid(TEST_SSID) + .setWpa2Passphrase(TEST_PRESHARED_KEY) + .setCredentialSharedWithUser(true) + .setOemPaid(true) + .build(); + } + + /** + * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception * when set both {@link WifiNetworkSuggestion.Builder#setIsInitialAutojoinEnabled(boolean)} * and {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} (boolean)} * to false on a passpoint suggestion. |